그 주식(에이치엘비 ?) 그때 샀어야 했는데…
안녕하세요 온룸입니다
오늘은 내가 어떤 주식을 어느시점에 샀을때 얼마나 벌었을까에 대한 파이썬 코드를 만들어 보겠습니다.
1. 라이브러리
기능 구현을 위해 쓰일 라이브러리 입니다. 참고로 환경은 Window 이고 Python 3.6 버전 32bit를 사용했습니다. (64bit도 상관없습니다.)
pip install lxml pip install pandas pip install requests pip install numpy
그리고 여기에 추가로 talib 가 필요합니다. TA-Lib는 기술적분석을 쉽게 도와주는 라이브러리 인데요 오버레이지표, 모멘텀 지표, 변동성지표, 패턴 인식 등을 지원합니다.
Window에 설치는 그냥 pip로는 설치가 되지 않습니다. 하지만 whl 파일을 다운 받으면 간단하게 설치 됩니다. 설치 방법은 아래와 같아요
- https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib 접속
- TA_Lib‑0.4.17‑cp36‑cp36m‑win32.whl 다운로드 (Python 3.6 64bit 는 amd64)
- 가상환경 enable 후 pip install TA_Lib-0.4.10-cp36-cp36m-win_win32.whl
이렇게 하면 설치 됩니다.
2. 결과
먼저 결과 부터 소개 하겠습니다. 다음에 소개해 드릴 코드를 돌려 보면 아래와 같이 출력 될겁니다. 그리고 이 결과는 아래 함수에서 도출 되게 되지요
main('005930', MACD, 1000000, '20121001', '20171117')
입력 Arguments는 순서대로 종목코드, 기술적분석 코드, 초기 투자금, 투자 시작일, 투자 종료일 입니다.
3. 코드
아래 코드입니다. 코드를 복붙해서 한번 돌려보세요 ㅎㅎ 그리고 그렇게 자세한 설명은 못했지만 대략적인 설명은 상단 영상 참조 바랍니다.
import pandas as pd import talib as TA import numpy as np import math from dateutil import relativedelta import requests import xml.etree.ElementTree as ET import datetime JONBER = 1 MACD = 3 RSI = 4 STOCH = 5 BUY = 2.0 SELL = -2.0 # 네이버에서 주가를 크롤링 하는 함수 def getStockValueFromNaverWithDate(stock_code, start, end): try: startDate = datetime.datetime.strptime(start, "%Y%m%d").date() endDate = datetime.datetime.strptime(end, "%Y%m%d").date() except: print("invalid date format ") return [] count = (endDate - startDate).days if count < 0: print("invalid Start, End Date") return [] url = 'https://fchart.stock.naver.com/sise.nhn?symbol=%s&timeframe=day&startTime=%s&count=%d&requestType=%d' % (stock_code, end, count, 2) print(url) r = requests.get(url) root = ET.fromstring(r.text) df_org = pd.DataFrame(columns=['Date', 'Open', 'High', 'Low', 'Close', 'AdjClose', 'Volume']) for data in root.findall("./chartdata/item"): stockVal = data.attrib['data'].split('|') stockVal[0] = datetime.datetime.strptime(stockVal[0], "%Y%m%d").date() stockVal.append(None) df_new = pd.DataFrame([stockVal], columns=['Date', 'Open', 'High', 'Low','AdjClose', 'Volume', 'Close']) df_org = df_org.append(df_new, ignore_index=True, sort= False) # 유효한 날짜의 주가만 추출 df_org = df_org[(df_org.Date >= startDate) & (df_org.Date <= endDate)] # STR 데이터를 숫자 타입으로 변환 df_org[["Open", "High", "Low", "AdjClose", "Volume"]] = df_org[ ["Open", "High", "Low", "AdjClose", "Volume"]].apply(pd.to_numeric) return df_org # se_line1, se_line2 의 Gold Cross 와 Dead Cross를 구한다. def getGoldDeadPosition(se_line1, se_line2): se_signal = np.sign(se_line1 - se_line2) se_signal[se_signal == 0] = 1 se_signal = se_signal - se_signal.shift() # 2, -2 로 구성된 시리즈 return se_signal def getGoldDeadLineBoundaryPosition(se_signal, base_signal, buy_line, sell_line): # 두개의 시그널 선 중 기준라인 사이의 값은 영으로 만든다 if buy_line != sell_line: se_signal.loc[(base_signal > buy_line) & (base_signal < sell_line)] = 0 # BUY LINE 밑에있는 매도 신호는 지운다 se_signal[(base_signal <= buy_line) & (base_signal != 0) & (se_signal == -2)] = 0 # SELL LINE 위에 있는 매수 신호는 지운다 . se_signal[(base_signal >= sell_line) & (base_signal != 0) & (se_signal == 2)] = 0 else: se_signal[(base_signal <= buy_line) & (base_signal != 0) & (se_signal == -2)] = 0 # SELL LINE 위에 있는 매수 신호는 지운다 . se_signal[(base_signal > sell_line) & (base_signal != 0) & (se_signal == 2)] = 0 se_signal[np.isnan(se_signal)] = 0.0 # testDf = pd.DataFrame({'A':se_signal, 'B':slowd, 'C':tmp_se_signal}) return se_signal # 기술적 분석에 따른 트레이드 포지션을 잡는 코드 def getTradePointFromMomentum(tech_anal_code, df_stock_val): base_line = [] df_stock_val['trade'] = 0 if tech_anal_code == JONBER: df_stock_val.loc[df_stock_val.index[0], 'trade'] = BUY df_stock_val.loc[df_stock_val.index[-1], 'trade'] = SELL # MACD elif tech_anal_code == MACD: se_macd, se_macdsignal, se_macdhist = TA.MACD(df_stock_val.AdjClose, fastperiod=12, slowperiod=26, signalperiod=9) se_macd = se_macd.round(2) se_macdsignal = se_macdsignal.round(2) tmpd = se_macd.copy() # MACD 가 Sig se_signal = getGoldDeadPosition(se_macd, se_macdsignal) se_signal = getGoldDeadLineBoundaryPosition(se_signal, tmpd, 0, 0) df_stock_val['trade'] = se_signal # -2.0 DeadCross 2.0 GoldCross SELL, BUY base_line = [0] elif tech_anal_code == RSI: real = TA.RSI(df_stock_val.AdjClose, timeperiod=14) se_30_sig = getGoldDeadPosition(real, 30) se_30_sig[se_30_sig == 2] = 0 se_30_sig[se_30_sig == -2] = 2 se_70_sig = getGoldDeadPosition(real, 70) se_70_sig[se_70_sig == -2] = 0 se_30_sig[se_70_sig == 2] = -2 se_signal = se_30_sig + se_70_sig df_stock_val['trade'] = se_signal base_line = [50] # STHOCH elif tech_anal_code == STOCH: SELL_LINE = 70 BUY_LINE = 30 slowk, slowd = TA.STOCH(df_stock_val.High, df_stock_val.Low, df_stock_val.AdjClose,\ fastk_period=12, slowk_period=5, slowk_matype=0, slowd_period=5, slowd_matype=0) slowk = slowk.round(2) slowd = slowd.round(2) tmpd = slowd.copy() se_signal = getGoldDeadPosition(slowk, slowd) # Slow D 가 25% 이하에서 %K 가 %d 를 상향 돌파시 매수 # Slow D 가 75% 이상에서 %K 가 %d 를 하향 돌파시 매도 se_signal= getGoldDeadLineBoundaryPosition(se_signal, tmpd, BUY_LINE, SELL_LINE) df_stock_val['trade'] = se_signal base_line = [50] print('Trade List : ') print(df_stock_val['trade'][df_stock_val['trade'] != 0.0]) print('-------------------------------------') return df_stock_val, base_line def doTrading(df_stock_val, balance): buyList = [] sellList = [] se_trade = df_stock_val['trade'][df_stock_val['trade'] != 0.0] if len(se_trade[se_trade == 2].index) == 0: return buyList, sellList, pd.Series(), pd.Series(), pd.Series() first_buy_idx = se_trade[se_trade == 2].index[0] df_stock_val['Balance'] = 0.0 df_stock_val['Asset'] = 0.0 df_stock_val['StockCount'] = 0.0 if first_buy_idx == 0: df_stock_val.loc[0, ['Balance', 'Asset', 'StockCount']] = balance, balance, 0 else: df_stock_val.loc[0:first_buy_idx , ['Balance', 'Asset', 'StockCount']] = balance, balance, 0 beforeIdx = first_buy_idx #for idx, value in se_trade.loc[first_buy_idx:].items(): se_idx_list = se_trade.loc[first_buy_idx:].index #print(se_idx_list) for idx, realIdx in enumerate(se_idx_list): if idx == 0 or \ se_trade.loc[realIdx] == BUY and \ se_trade.loc[beforeIdx] != se_trade.loc[realIdx]: stock_count = math.floor(balance / df_stock_val.loc[realIdx].AdjClose) balance -= stock_count * df_stock_val.loc[realIdx].AdjClose #print('buy ', 'Before Trade IDX ', beforeIdx, 'Current Trade IDX: ', realIdx, 'Stock Price: ', df_stock_val.loc[realIdx].AdjClose, # 'Stock Count : ', stock_count, 'balance: ', balance) if idx == len(se_idx_list) - 1: df_stock_val.loc[realIdx: , ['Balance', 'Asset', 'StockCount']] = \ balance, balance + (df_stock_val.loc[realIdx:]['AdjClose']* stock_count), stock_count #print('end BUY') else: next_idx = idx + 1 for remain_idx in range(idx+1, len(se_idx_list)): if se_trade.loc[se_idx_list[remain_idx]] == SELL: next_idx = remain_idx break if remain_idx == len(se_idx_list)-1: df_stock_val.loc[realIdx:, ['Balance', 'Asset', 'StockCount']] = \ balance, balance + (df_stock_val.loc[realIdx:][ 'AdjClose'] * stock_count), stock_count else: df_stock_val.loc[realIdx:se_idx_list[next_idx], ['Balance', 'Asset', 'StockCount']] = \ balance, balance + (df_stock_val.loc[realIdx:se_idx_list[next_idx]]['AdjClose'] * stock_count), stock_count # print('NOW BUY NEXT Trade IDX ', next_idx) buyList.append([ df_stock_val.loc[realIdx].Date.strftime("%Y-%m-%d"), df_stock_val.loc[realIdx].AdjClose ]) beforeIdx = realIdx elif se_trade.loc[realIdx] == SELL and \ se_trade.loc[beforeIdx] != se_trade.loc[realIdx]: stock_count = df_stock_val.loc[realIdx]['StockCount'] balance += stock_count * df_stock_val.loc[realIdx].AdjClose asset = balance stock_count = 0 # print('sell ', 'Before Trade IDX ', beforeIdx, 'Current Trade IDX: ', realIdx, 'Stock Price: ', # df_stock_val.loc[realIdx].AdjClose, 'Stock Count : ', stock_count, 'balance: ', balance) if idx == len(se_idx_list) - 1: df_stock_val.loc[realIdx:, ['Balance', 'Asset', 'StockCount']] = \ balance, asset, stock_count # print('end SELL') else: next_idx = idx + 1 for remain_idx in range(idx+1, len(se_idx_list)): if se_trade.loc[se_idx_list[remain_idx]] == BUY: next_idx = remain_idx break if remain_idx == len(se_idx_list) - 1: df_stock_val.loc[realIdx:, ['Balance', 'Asset', 'StockCount']] = \ balance, asset, stock_count else: df_stock_val.loc[realIdx:se_idx_list[next_idx], ['Balance', 'Asset', 'StockCount']] = \ balance, asset, stock_count # print('NOW SELL NEXT Trade IDX ', next_idx) sellList.append([ df_stock_val.loc[realIdx].Date.strftime("%Y-%m-%d"), df_stock_val.loc[realIdx].AdjClose ]) beforeIdx = realIdx print(df_stock_val[['Date', 'Open', 'High', 'Low','AdjClose']].head(3)) print(df_stock_val[['Date', 'Open', 'High', 'Low','AdjClose']].tail(3)) # for i in df_stock_val['Asset'].iteritems(): # print(i) return buyList, sellList, df_stock_val['Balance'], df_stock_val['Asset'], df_stock_val['StockCount'] def getInvestPeriod(startDate, EndDate): r = relativedelta.relativedelta(EndDate, startDate) if r.years: period_str = '%d년 %d개월 %d일' % (r.years, r.months, r.days) elif r.months: period_str = '%d개월 %d일' % (r.months, r.days) elif r.days: period_str = '%d일' % (r.days) return period_str def main(stockCode, techAnalCode, balance, start, end): # 네이버에서 주가를 가져옴 df_stock_val = getStockValueFromNaverWithDate(stockCode, start, end) if len(df_stock_val) == 0: return # 기술적 분석에 따른 트레이드 포지션을 잡는 코드 df_stock_val, base_line = getTradePointFromMomentum(techAnalCode, df_stock_val) # 백테스팅 진행 doTrading(df_stock_val, balance) org_asset = df_stock_val.iloc[0].Asset last_asset = df_stock_val.iloc[-1].Asset added_asset = last_asset - org_asset final_yield = 100 * (added_asset / org_asset) str_invest_period = getInvestPeriod(df_stock_val.iloc[0].Date, df_stock_val.iloc[-1].Date) print('++++++++++결과+++++++++++++') print('초기 자산 : ', org_asset) print('마지막 자산 : ', last_asset) print('증가된 자산 : ', added_asset) print('수익률 : ', final_yield, '%') print('투자일시 : ', df_stock_val.iloc[0].Date, '~', df_stock_val.iloc[-1].Date) print('투자기간 : ', str_invest_period) if __name__ == '__main__': main('005930', MACD, 1000000, '20121001', '20171117') #print(getStockValueFromNaverWithDate('005930', '20011001', '20021101'))
4. 웹사이트
혹시 코드짜기 귀찮으신 분들을 위해 똑같은 기능을 하는 웹사이트를 만들어 봤습니다. 이름하야 stocklab ㅋㅋㅋㅋ 아래 URL로 접속하시면 됩니다.
https://lifeonroom.com/stockpy/stocks/if-i-bought/
사진은 삼성전자 인데 에이치엘비로 돌려보니 후덜덜한 수익률이 나오더라구요 ㅋㅋ
참고로 웹사이트의 프레임워크는 Django를 사용했으며 해당 블로그가 돌아가는 서버에서 워드프레스와 함께 돌아가고 있습니다.
넵 오늘은 여기 까지 입니다.
주식차트의 지나온 그래프를 보면 아쉬움이 들때도 있고 안도감이 들 때도 있죠 ㅎㅎ
파이썬 코드 또는 Stocklab을 통해 더 정확한 후회의 한숨과 안도의 한숨을 쉬어 보시기 바랍니다. ㅋㅋ
블로그 구독하기 !!