2. 주식데이터 가지고 오기
소스파일은 github.com/galaxywiz/StockCrawler_py 에서 확인 가능합니다.
주식 트레이딩에 성공적인 사람들은 대부분 장기 투자자입니다.
워렌버핏도 10년이상 볼 것이 아니면 10분도 가지지 말라는 말을 한적이 있죠.
저도 처음에는 1일, 1분, 3분, 5분봉 보면서 단타를 해봤지만, 결국 다 잃고,
1년 이상 홀딩 한 종목이 엄청난 수익을 안겨주면서 위 말을 믿고 있습니다.
굳이 이런 말을 하는 이유는 구태여 분봉 데이터를 얻어서 트레이딩 하는 건 해외선물이 아닌 이상의미가 없다는 걸 말씀드리고 싶었습니다.
우선 인터넷에 흔히 있는 데이터를 모으는 것부터 시작합시다.
보통 인터넷에선 네이버나 야후 파이낸셜 데이터를 가지고 오는데, 저는 야후 쪽을 사용합니다.
왜냐면, 미국 주식을 가지고 오기도 편하고, 주식 분할 등을 했을 경우,
주식 분할 전 데이터도 다 분할해서 데이터를 전달해 주기 때문에, 정확한 데이터를 얻어 오기 좋습니다.
Crawler 라고 수집 클래스를 아래와 같이 작성합니다.
stockCrawler.py
# 인터넷에서 주식 데이터를 가지고 옵니다.
# 한국주식, 미국주식
#
import pandas as pd
from pandas import Series, DataFrame
from pandas_datareader import data as web
import numpy as np
import dataframe
import urllib.request
from bs4 import BeautifulSoup
import os
import time
import yfinance as yf
from datetime import datetime
from datetime import timedelta
#----------------------------------------------------------#
# 주식 목록 갖고오기 (상위)
class StockCrawler:
def __getNaverURLCode(self, code):
url = 'http://finance.naver.com/item/sise_day.nhn?code={code}'.format(code=code)
print("요청 URL = {}".format(url))
return url
# 종목 이름을 입력하면 종목에 해당하는 코드를 불러오기
def __getNaverStockURL(self, item_name, stockDf):
code = stockDf.query("name=='{}'".format(item_name))['code'].to_string(index=False)
url = self.__getNaverURLCode(code)
return url
# 네이버에서 주식 데이터를 긁어 온다.
def getKoreaStockDataFromNaver(self, ticker, loadDays):
# 일자 데이터를 담을 df라는 DataFrame 정의
df = pd.DataFrame()
try:
url = self.__getNaverURLCode(ticker)
loadDays = (loadDays / 10) + 1
# 1페이지가 10일. 100페이지 = 1000일 데이터만 가져오기
for page in range(1, int(loadDays)):
pageURL = '{url}&page={page}'.format(url=url, page=page)
df = df.append(pd.read_html(pageURL, header=0)[0], ignore_index=True)
# df.dropna()를 이용해 결측값 있는 행 제거
df = df.dropna()
df.reset_index(inplace=True, drop=False)
stockDf = pd.DataFrame(df, columns=['날짜', '시가', '고가', '저가', '종가', '거래량'])
stockDf.rename(columns={'날짜': 'candleTime', '고가': 'high', '저가': 'low', '시가': 'start', '종가': 'close', '거래량' : 'vol'}, inplace = True)
stockDf['candleTime'] = stockDf['candleTime'].str.replace(".", "-")
print(stockDf)
return stockDf
except:
return None
#파일에서 로딩할떄 “종목:코드:순위” 형식으로 로딩
def _loadFromFile(self, targetList):
stockDf = DataFrame(columns = ("name", "code","ranking"))
for text in targetList:
tokens = text.split(':')
row = DataFrame(data=[tokens], columns=["name", "code","ranking"])
stockDf = stockDf.append(row)
stockDf = stockDf.reset_index(drop=True)
return stockDf
#일부 종목만 지정해서 로딩하고 싶을 때
def getStocksListFromFile(self, fileName):
with open(fileName, "r", encoding="utf-8") as f:
targetList = f.read().splitlines()
return self._loadFromFile(targetList)
#----------------------------------------------------------#
### 아후에서 데이터 긁어오기.
class KoreaStockCrawler(StockCrawler):
def __getTicker(self, ticker):
# 코스피는 KS, 코스닥은 KQ
t = ticker + ".KS"
try:
p = web.get_quote_yahoo(t)['marketCap']
return t
except:
return ticker +".KQ"
def getStockData(self, ticker, loadDays):
rowTicker = ticker
try:
ticker = self.__getTicker(ticker)
oldDate = datetime.now() - timedelta(days=loadDays)
fromDate = oldDate.strftime("%Y-%m-%d")
stockDf = web.DataReader(ticker, start = fromDate, data_source='yahoo')
if len(stockDf) == 0:
return self.getKoreaStockDataFromNaver(rowTicker, loadDays)
stockDf.reset_index(inplace=True, drop=False)
stockDf.rename(columns={'Date': 'candleTime', 'High': 'high', 'Low': 'low', 'Open': 'start', 'Close': 'close', 'Volume' : 'vol'}, inplace = True)
stockDf['candleTime'] = stockDf['candleTime'].dt.strftime("%Y-%m-%d")
stockDf = pd.DataFrame(stockDf, columns=['candleTime', 'start', 'high', 'low', 'close', 'vol'])
print(stockDf)
return stockDf
except:
return self.getKoreaStockDataFromNaver(rowTicker, loadDays)
def getStocksList(self, limit):
# 한국 주식 회사 등록 정보 가지고 오기
stockDf = pd.read_html('http://kind.krx.co.kr/corpgeneral/corpList.do?method=download&searchType=13', header=0)[0]
stockDf.종목코드 = stockDf.종목코드.map('{:06d}'.format)
stockDf = stockDf[['회사명', '종목코드']]
stockDf = stockDf.rename(columns={'회사명': 'name', '종목코드': 'code'})
# 시총 구하기
marketCapList = []
ranking = []
dropIdx = []
for idx, row in stockDf.iterrows():
try:
ticker = self.__getTicker(row['code'])
p = web.get_quote_yahoo(ticker)['marketCap']
marketCap = int(p.values[0])
marketCapList.append(marketCap)
except:
dropIdx.append(idx)
print("[%s][%s] 시총 갖고오기 에러" % (row['name'], row['code']))
stockDf.drop(dropIdx, inplace = True)
rank = 1
for i in marketCapList:
ranking.append(rank)
rank += 1
stockDf['MarketCap'] = marketCapList
stockDf = stockDf.sort_values(by='MarketCap', ascending=False)
stockDf['ranking'] = ranking
if limit > 0:
stockDf = stockDf[:limit]
return stockDf
# 미국 주식 긁어오기
class USAStockCrawler(StockCrawler):
def getStockData(self, ticker, loadDays):
oldDate = datetime.now() - timedelta(days=loadDays)
fromDate = oldDate.strftime("%Y-%m-%d")
try:
stockDf = web.DataReader(ticker, start = fromDate, data_source='yahoo')
stockDf.reset_index(inplace=True, drop=False)
stockDf.rename(columns={'Date': 'candleTime', 'High': 'high', 'Low': 'low', 'Open': 'start', 'Close': 'close', 'Volume' : 'vol'}, inplace = True)
stockDf['candleTime'] = stockDf['candleTime'].dt.strftime('%Y-%m-%d')
features =['candleTime', 'start', 'high', 'low', 'close', 'vol']
stockDf = stockDf[features]
print(stockDf)
return stockDf
except:
print("[%s] Ticker load fail" % ticker)
return None
def getStocksList(self, limit):
sp500 = pd.read_html("https://en.wikipedia.org/wiki/List_of_S%26P_500_companies")[0]
sp500 = sp500[['Security', 'Symbol']]
sp500 = sp500.rename(columns={'Security': 'name', 'Symbol': 'code'})
nasdaqDf = pd.read_html("https://en.wikipedia.org/wiki/NASDAQ-100#External_links")[3]
nasdaqDf = nasdaqDf.rename(columns={'Company': 'name', 'Ticker': 'code'})
stockDf = sp500.append(nasdaqDf)
stockDf = stockDf.drop_duplicates(['code'], keep='last')
# 시총 구하기
marketCapList = []
ranking = []
dropIdx = []
for idx, row in stockDf.iterrows():
try:
tickers = row['code']
p = web.get_quote_yahoo(tickers)['marketCap']
marketCap = int(p.values[0])
marketCapList.append(marketCap)
except:
dropIdx.append(idx)
marketCapList.append(0)
print("[%s][%s] 시총 갖고오기 에러" % (row['name'], row['code']))
rank = 1
for i in marketCapList:
ranking.append(rank)
rank += 1
stockDf['MarketCap'] = marketCapList
stockDf = stockDf.sort_values(by='MarketCap', ascending=False)
stockDf['ranking'] = ranking
stockDf.drop(dropIdx, inplace = True)
if limit > 0:
stockDf = stockDf[:limit]
print(stockDf)
return stockDf
각각의 클래스는 StockCrawler 라는 상위 공통 집합이 있고, 이를 상속받는 KoreaStockCrawler (74라인) 와 USAStockCrawler (142라인) 클래스를 정의했습니다.
각 클래스들은 공통의 함수 getStockList 와 getStockData를 갖고 있습니다. 또한 공통 기능으로 StockCrawler에 getStockListFromFile 함수가 있습니다.
이제 위 3함수는 아래와 같은 기능으로 작성되어 있습니다.
KoreaStockCrawler에 대한 기술을 읽어 보시고, USAStockCrawler 에서 어떻게 응용하고 어느 부분이 바뀌었는지 보시면 큰 도움이 되실 것 같습니다.
getStockList, 입력 파라메터 limit |
108줄을 보시면 http://kind.krx.co.kr/corpgeneral/corpList.do?method=download&searchType=13 라는 페이지를 읽어 오는데 실제 이 링크를 웹브라우저에 입력해 보시면 한국 기업 공시 채널 홈페이지의 상장목록 파일을 다운 받을 수 있습니다. 이 글을 쓰고 있는 2020-10-20일 최근 상장한 빅히트 종목도 있네요. 111라인까지는 컬럼명을 프로그램 사용에 맞게 통일하는 작업 정도입니다. 이대로 return 해도 되겠지만, 여기선 시총을 구해서 시총 순으로 정렬하여 리턴 하고자 합니다. 시총을 구하는 이유는 아무리 그래프가 좋아도 시총이 작으면 작전 세력의 먹이가 될 수 있기 때문에 이런 불확실한 종목은 배제하기 위함입니다. 어느 정도 시총이 크면 작전 세력들이 주가를 흔들기 어렵기 떄문에 큰 시총에 투자하는 게 안전하다고 생각하고, 개인적으로 한국 주식은 시총 200위 정도면 볼만하고 그 이하는 프로그래밍 매매하기 위험하다고 생각합니다. 위에 limit 파라메터 받아오는데, 이는 시총 몇 위까지 잘라서 리턴 하기 위함입니다.
그럼 시총을 어떻게 구하는가? 이는 119라인부터 기술되어 있습니다. get_quote_yahoo라고, 야후에서 시총 항목을 구하는 함수가 있는데, 그리고 129라인에 1부터 차례대로 증가하는 배열을 만들고 |
getStockListFromFile 입력 파라메터 filename |
67라인에 파일로부터 주식 목록을 가지고 오는 함수를 정의했습니다. 웹에서 매일 가지고 오기 귀찮고, 내가 알고 있는 주식에 대해서만 (사실 이게 가장 정답인 것 같아요) 모니터닝 하고자 할 때 사용하는 함수입니다. 파라메터로 file이름을 넘겨주면, 그 파일을 열어서 종목:코드:순위 로 읽어 옵니다. 파일 기술은 오른쪽 그림과 같이 정의합니다 (개인적으로 많이 보고 있는 주식들입니다). |
getStockData 파라메터 ticker, loadDays |
주식의 일자, 시가, 고가, 저가, 종가, 거래량을 웹에서 얻어오는 함수입니다. 우선 88라인 보시면 내부 함수로 (밖에서 못쓰는 함수) code를 ticker 로 변환합니다. 이후 89라인으로 얼마만큼 데이터를 가지고 올지 startDate 를 구하고, 91 라인으로부터 yahoo에서 주식 데이터를 얻어 옵니다. 만약 얻어오는데 실패했다면, except: 문을 따라104라인에 네이버 웹에서 데이터를 얻어 오도록 하였습니다. 96 라인 부터는 컬럼명을 프로그램에 맞게 재조정하는 코드입니다. |