STOCK/볼린저밴드

[파이썬 주식] 볼린저밴드 - 2. 금일 추세추종 종목찾기

BOTTLE6 2021. 2. 20. 14:21

1. 전체 종목 중 금일 볼린저 밴드의 추세추종 "매수조건"에 만족하는 종목을 찾아서 반환한다.

2. 추세추종 조건 만족하는 종목 중 "매수조건", "매도조건"에 대해 백테스트하여 수익률 여부 체크

3. 볼린저 밴드 그려보기

 

### 1. 라이브러리 임포트

from Investar import Analyzer #파이썬증권데이터분석 책 참고
mk = Analyzer.MarketDB() 
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import time
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
# matplotlib 한글 폰트 출력코드

import matplotlib
from matplotlib import font_manager, rc
import platform

try : 
    if platform.system() == 'Windows':
    # 윈도우인 경우
        font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
        rc('font', family=font_name)
    else:    
    # Mac 인 경우
        rc('font', family='AppleGothic')
except : 
    pass
matplotlib.rcParams['axes.unicode_minus'] = False   

 

### 2. 종목 불러오기

## 종목 불러오기
import pandas as pd
"""KRX로부터 상장기업 목록 파일을 읽어와서 데이터프레임으로 반환"""
url = 'http://kind.krx.co.kr/corpgeneral/corpList.do?method='\
    'download&searchType=13'
krx = pd.read_html(url, header=0)[0]
krx = krx[['종목코드', '회사명']]
krx = krx.rename(columns={'종목코드': 'code', '회사명': 'company'})
krx.code = krx.code.map('{:06d}'.format)
krx = krx.set_index('code')
krx

 

### 3. 볼린저밴드 추세추종 매수신호 찾기

def 볼린저밴드_추세추종_매수신호찾기(종목명):
    df = mk.get_daily_price(종목명, '2020-01-01')
    df.set_index(df['date'].apply(lambda x:pd.to_datetime(x)),inplace=True)
    df['내일시가'] = df['open'].shift(-1)
    df['MA20'] = df['close'].rolling(window=20).mean() 
    df['stddev'] = df['close'].rolling(window=20).std() 
    df['upper'] = df['MA20'] + (df['stddev'] * 2)
    df['lower'] = df['MA20'] - (df['stddev'] * 2)
    df['PB'] = (df['close'] - df['lower']) / (df['upper'] - df['lower'])
    df['TP'] = (df['high'] + df['low'] + df['close']) / 3
    df['PMF'] = 0
    df['NMF'] = 0
    for i in range(len(df.close)-1):
        if df.TP.values[i] < df.TP.values[i+1]:
            df.PMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
            df.NMF.values[i+1] = 0
        else:
            df.NMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
            df.PMF.values[i+1] = 0
    df['MFR'] = (df.PMF.rolling(window=10).sum() /
        df.NMF.rolling(window=10).sum())
    df['MFI10'] = 100 - 100 / (1 + df['MFR'])
    df = df[19:]
    
    cond1 = (df['PB']>0.8) & (df['MFI10']>80)
    cond2 = (df['PB']<0.2) & (df['MFI10']<20)
    df.loc[cond1, "매수"] = 1
    df.loc[cond2, "매도"] = -1 #매도시 -1
    df=df.fillna(0)

    df['신호'] = df['매수']+df['매도']
    상태 = []
    매수시점 = []
    매도시점 = []
    
    for i in range(len(df.close)):
        if i==0:
            상태.append(df["신호"][i]+0)            
        else :
            상태.append(df["신호"][i]+상태[i-1])
        if 상태[i] > 1:
            상태[i] = 1
        elif 상태[i] < 0:
            상태[i] = 0
    for i in range(len(df.close)):        
        if i==0:
            매수시점.append(상태[0])
            매도시점.append(0)
        else:
            if (df["신호"][i]==1) & (상태[i-1]==0) : #신호는 매수 이며 현재 상태는 아무것도 아닌상태일때
                매수시점.append(1)
                매도시점.append(0)
            elif (df["신호"][i]==-1) & (상태[i-1]==1) : #신호는 매도 -1이며 현재 상태는 매수한 상황일떄1   
                매수시점.append(0)
                매도시점.append(-1)
            else:
                매수시점.append(0)
                매도시점.append(0)
    if 상태[-1]==1:
        매도시점[-1]=-1 #마지막은 매도
    df['상태']=상태[:]
    df['매수시점']=매수시점[:]
    df['매도시점']=매도시점[:]
    
    매수단가 = list(df[df['매수시점']==1].close.values) 
    매도단가 = list(df[df['매도시점']==-1].close.values)
    수익률=[]
    for i in range(len(매수단가)):
        수익률.append(매도단가[i] / 매수단가[i] *0.9975 - 0.004)
    
    매수신호 = 0
    매도신호 = df[-40:-2].매도.sum()
    과거매수 = df[-10:-2].매수.sum() 
    if (매도신호 <= -1) & (과거매수 < 1):
        매수신호 = df[-2:].매수.sum()
    
    if (df['상태'][-1]==1) & (df['상태'][-2]==0): #마지막날 매수시점으로 나왔을 때, 
        검토대상=1
    else:
        검토대상=0
    
    #print(수익률)
    from functools import reduce
    def multiply(arr):
        return reduce(lambda x, y: x * y, arr)
    누적수익률 = multiply(수익률)
    기하평균수익률 = 누적수익률**(1/len(수익률))
    #print("누적수익률 : {:.0%}".format(누적수익률))
    #print("기하평균수익률 : {:.0%}".format(기하평균수익률))
    
    #return 수익률
    return 종목명, len(수익률), 누적수익률, 기하평균수익률, 검토대상


# 매수신호 찾기
import time
start = time.time()
companys = krx['company']
종목명 = []
횟수 = []
누적수익률 = []
기하평균수익률 = []
검토대상 = []
for company in companys:
    try:
        a,b,c,d,e = 볼린저밴드_추세추종_매수신호찾기(company)
        종목명.append(a)
        횟수.append(b)
        누적수익률.append(c)
        기하평균수익률.append(d)
        검토대상.append(e)
        
    except:
        pass
df = pd.DataFrame({"횟수":횟수, "누적수익률":누적수익률, "기하평균수익률":기하평균수익률, "검토대상":검토대상},index=종목명)
print("완료 소요시간 :", time.time() - start)  # 현재시각 - 시작시간 = 실행 시간

      ○ 매수조건 : PB > 0.8 & MFI > 0.8 

      ○ 매도조건 : PB < 0.6 & MFI < 0.2

■ 어제까지 매도조건 이후로 매수조건 만족하지 않았으나, 오늘 매수조건을 만족한 경우, 검토대상=1로 반환

※ 강한 매수조건 추세추종이 반복될 경우, 여러번 매수조건 신호가 반복되므로 오늘자 매수신호가 최근 첫번째 매소신호 일때 만을 검토대상=1로 반환

 

이번 주 핫했던, 다날도 지난주 금요일? 기준으로 매수신호 최초 발생하였었음

codes = list(df[(df['검토대상']==1)&(df['기하평균수익률']>1.05)].index)
df.loc[codes].sort_values(by="기하평균수익률",ascending=False)

 

종목명 : 'DSR', '인터지스', '엑시콘', '뉴파워프라즈마', '인포마크', '한국정보인증', '한글과컴퓨터', '동양생명', '피엔아이컴퍼니', '부산주공', 'DGB금융지주', '드림시큐리티'

 

### 4. 볼린저밴드 그려보기

def 볼린저밴드_추세추종(종목명):
    df = mk.get_daily_price(종목명, '2020-01-01')

    df['MA20'] = df['close'].rolling(window=20).mean() 
    df['stddev'] = df['close'].rolling(window=20).std() 
    df['upper'] = df['MA20'] + (df['stddev'] * 2)
    df['lower'] = df['MA20'] - (df['stddev'] * 2)
    df['PB'] = (df['close'] - df['lower']) / (df['upper'] - df['lower'])
    df['TP'] = (df['high'] + df['low'] + df['close']) / 3
    df['PMF'] = 0
    df['NMF'] = 0
    for i in range(len(df.close)-1):
        if df.TP.values[i] < df.TP.values[i+1]:
            df.PMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
            df.NMF.values[i+1] = 0
        else:
            df.NMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
            df.PMF.values[i+1] = 0
    df['MFR'] = (df.PMF.rolling(window=10).sum() /
        df.NMF.rolling(window=10).sum())
    df['MFI10'] = 100 - 100 / (1 + df['MFR'])
    df = df[19:]

    plt.figure(figsize=(9, 8))
    plt.subplot(2, 1, 1)
    plt.title('{} Bollinger Band(20 day, 2 std) - Trend Following'.format(종목명))
    plt.plot(df.index, df['close'], color='#0000ff', label='Close')
    plt.plot(df.index, df['upper'], 'r--', label ='Upper band')
    plt.plot(df.index, df['MA20'], 'k--', label='Moving average 20')
    plt.plot(df.index, df['lower'], 'c--', label ='Lower band')
    plt.fill_between(df.index, df['upper'], df['lower'], color='0.9')
    for i in range(len(df.close)):
        if df.PB.values[i] > 0.8 and df.MFI10.values[i] > 80:       # ①
            plt.plot(df.index.values[i], df.close.values[i], 'r^')  # ②
        elif df.PB.values[i] < 0.2 and df.MFI10.values[i] < 20:     # ③
            plt.plot(df.index.values[i], df.close.values[i], 'bv')  # ④
    plt.legend(loc='best')

    plt.subplot(2, 1, 2)
    plt.plot(df.index, df['PB'] * 100, 'k', label='%B x 100')       # ⑤ 
    plt.plot(df.index, df['MFI10'], 'g--', label='MFI(10 day)')     # ⑥
    plt.yticks([-20, 0, 20, 40, 60, 80, 100, 120])                  # ⑦
    for i in range(len(df.close)):
        if df.PB.values[i] > 0.8 and df.MFI10.values[i] > 80:
            plt.plot(df.index.values[i], 0, 'r^')
        elif df.PB.values[i] < 0.2 and df.MFI10.values[i] < 20:
            plt.plot(df.index.values[i], 0, 'bv')
    plt.grid(True)
    plt.legend(loc='best')
    plt.savefig('./result/bollinger/20210217_{}.png'.format(code))
    plt.show();  

codes = list(df.sort_values(by="기하평균수익률",ascending=False)[(df['검토대상']==1)&(df['기하평균수익률']>1.05)].index)
for code in codes :
    볼린저밴드_추세추종(code)