한국이나 미국 주식을 인터넷 사이트에서 볼려면 네이버나 야후 파이낸스를 들어간다. 하지만 중국은 그런 서비스를 제공해주는 사이트가 매우 많다. 예를 들면 同花顺,东方财富网,新浪财经 이 3군데가 가장 유명하다.바이두에 주식 티커를 검색하면 이 3개 사이트들을 기반으로 해당 정보를 제공해준다. 근데 문제는 중국 주식시장이 워낙 복잡하다보니, 사이트에서 원하는 정보를 찾기도 그렇게 쉽진않다. 한마디로 정보가 너무 많아서 원하는 것을 바로바로 찾기가 상대적으로 어렵다고 느껴진다. 디자인이 깔끔하지 않은 것도 한 몫한다.
그래서 그런지 몰라도, 크롤링 난이도도 뒤따라 상승하는 듯 하다. 물론, 사용하는 언어가 R이라서 그런 것일 수도 있다.크롤링 툴/정보/포스트를 기준으로 보면 R보다 Python이 크롤링에 적합하긴 하다고 생각한다. 그러나 둘의 기본적인 맥락은 비슷하다.
아무튼, 이번 포스트는 중국 주식 가격을 어떻게 크롤링하는지 간단하게 써볼려고 한다. 덤으로 "분석"이라고 부르기엔 매우 민망한, 간단한 변동성 분석도 해보고.
참고로, 지난번 포스트에 사용했던 getSymbols를 사용하면 안되는가?라는 질문에 답하자면, 해도된다.근데 여기선 그냥 중국 주식 사이트에 들어가서 크롤링을 한번 해보겠다. 굳이 이렇게 어렵게 하는 이유는, 이후에 재무제표등 크롤링을 하기위해 사이트 구조를 알아야할 필요가 있기도 하고, 이렇게 한번 만들어두고 조금씩 수정하면서 계속 쓸 수 있기때문이다.
library(rvest)
library(htmltab)
library(stringr)
library(readr)
library(httr)
library(xts)
library(magrittr)
library(PerformanceAnalytics)
library(ggplot2)
test <- c("600371","603613","688559","600531","603313","600597","603025","601899","600310",
"601002","600789","601318","600986","600908","600190","601166","600036","600118",
"600271","603977","600011","600519","688179","600707","600079")
length(test) # 25개 종목
k <- 1
i <- 0
start_time <- Sys.time()
for(k in 1:length(test)){
df <- NULL
Sys.setlocale("LC_ALL", "English")
# 사이트 : 网易财经
url <- paste0('http://quotes.money.163.com/trade/lszjlx_',test[k],',',i,'.html')
# 끝 페이지 위치 가져오기
data_page = read_html(url, encoding = 'UTF-8') %>%
html_node('.area') %>%
html_node('.innner_box') %>%
html_node('.mod_pages') %>%
html_children() %>%
html_text()
# 끝 페이지 번호 추출
endpoint <- as.numeric(data_page[length(data_page) - 1])
# 1 페이지당 50행 = 50일 ------> 약 5페이지면 1년(영업일 기준)
one_year_data <- 5-1
final_point <- min(one_year_data,endpoint)
if(final_point < 4){
next
}
# 종가 데이터 추출
for(i in 0:final_point){
url <- paste0('http://quotes.money.163.com/trade/lszjlx_',test[k],',',i,'.html')
stock_h <- GET(url,user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'))
Sys.setlocale("LC_ALL", "Chinese")
stock_data <- stock_h %>% read_html() %>% html_table(fill = T)
price_data <- stock_data[[4]]
row.names(price_data) <- NULL
row.names(price_data) <- price_data[,1]
price_data[,1] <- NULL
price <- price_data[1]
df <- rbind(df,price)
cat("\n",i + 1," page complete")
Sys.sleep(runif(1)*3)
}
# 열 이름 변경
colnames(df) <- c("종가")
# ,(콤마) 제거
df[,1] <- gsub(",","",df[,1])
# 주가 데이터 저장
setwd(paste0("")) # 저장하려는 폴더명 넣기
write.csv(df,paste0(test[k],".csv"),row.names = T)
cat("\n", test[k],"complete ,",length(test)-k,"left")
}
# 걸린 시간
end_time <- Sys.time()
end_time - start_time # Time difference of 4.502957 mins
1.여기서 고른 25개 종목들은 임의로 선택한 것이다. 좋은 주식인지는 나도 모른다. 그리고 user_agent 없이 계속 데이터를 가져왔더니 중간에 Sys.sleep을 넣어도 Time Connection Error가 발생한다. 이 점을 주의하자.
2.해당 코드에선 1년치 데이터만 가져왔다. 아쉬운 점은 누락된 데이터가 매 종목마다 달라서 나중에 전처리하기가 짜증날 것이다.
3.주가가 1000위안을 안넘으면 괜찮은데, 간혹가다 넘는 주식이 생기면 ","를 전처리 해줘야한다. string data라 없애지 않으면 numeric으로 바꾸기 힘들다
4.R에서 크롤링을 할 때,saving 항목에서의 encoding을 알맞게 했는데도 글자가 깨지는 현상이 발생할 수 있다.그 때는 Sys.setlocale 함수를 이용해서 데이터를 읽어들이고 출력해야한다.
5.걸린 시간은 약 4분이다.
## 데이터들을 저장한 폴더에서 데이터들을 읽어오기
price_list <- list()
for (t in 1:length(test)){
tryCatch({
name <- test[t]
price_list[[t]] <- read.csv(paste0(name,'.csv'),row.names = 1) %>% as.xts()
colnames(price_list[[t]]) <- name
cat("\n",name," has been copied. and",t,"is the number")
}, error = function(e){
warning(paste0("Ticker ",name," does not exist."))
})
}
### 각 주가 데이터를 합치기
price_list <- do.call(cbind,price_list) %>% na.locf()
1.주의할 점은, 앞에서 말했듯이 주가 데이터를 do.call로 강제로 합치면 누락된 데이터들 때문에 NA가 발생한다. 각 주식마다 누락된 데이터들의 날짜가 다르다. 그런데 각 주식의 데이터 총 갯수는 다 똑같고 데이터 시작 날짜가 다 다르다. (데이터가 끝나는 날짜는 모두 동일한데, 중간마다 누락된 데이터의 날짜가 종목마다 다 달라서 주식 데이터의 시작날짜가 다 다르다).이런 상황에서 강제로 cbind를 해버리면 NA가 발생할 수 밖에 없다. 이 문제는 밑에서 해결할 것이다.
2.trycatch를 사용한 이유는, 위 코드에서 1년을 기준으로 데이터를 가져왔는데, 만약에 데이터가 1년치보다 적으면 자연스럽게 건너뛰도록 설계를 했기때문에 그렇다. 즉 test에서의 종목들 중에 신생기업이 있어서 데이터가 매우 적으면 데이터를 저장을 안했을 것이므로, 그런 종목 코드들은 trycatch로 패스하게 해주는 것이다.
이제 데이터 자체는 다 불러왔으므로 변동성 분석을 간단하게 해보겠다.여기서 말하는 변동성 분석은 종목의 변동성 크기를 관찰하는 전략이다. 그럼 변동성이 주식에 있어서 왜 중요한가?를 물어본다면, 변동성은 주식에게 있어서 위험이라는 개념과 대응되기 때문이다. "주식 가격이 하루에 얼마나 움직이는가?"를 나타내는 지표가 바로 변동성, 수학적으로 표현하면 표준편차 or 분산에 해당한다. 일반적으로 표준편차를 사용하는 편이다. 그러면 상식적으로 변동성, 즉 표준편차가 크면 주가 등락폭이 매우 큰 편일 것이고, 표준편차가 작으면 주가 등락폭이 매우 작을 것이다. 이러한 현상은 High Risk, High Return의 원칙에 제대로 부합한다. 따라서 변동성이 큰 종목의 기대수익률은 상대적으로 크고, 변동성이 작은 종목의 기대수익률은 상대적으로 작을 것이다.이는 CAPM 모형에서도 충분히 직관적으로 파악이 가능한 사실이다.
위 그림에서도 알 수 있듯이, 표준편차(Standard Deviation)이 증가할수록, 기대수익률(Expected Return)도 따라서 커진다.물론 표준편차,즉 변동성이 커질수록 위험도 커지므로 시장 포트폴리오(Market Portfolio, 위 그림에선 Tangency Portfolio로 표현됨)에 멀어지지만, 동시에 High Return을 얻을 수 있는 기회이기도하다.
문제는, 현실에선 이런 재무학적 이론을 위배하는 현상이 나타난다는 점이다. 원래대로라면 변동성이 높은 종목들의 기대수익률이 변동성이 낮은 종목들의 기대수익률보다 높아야하는데, 실제론 반대 현상이 시장에서 목격되고 있다. 즉, 저변동성 주식들이 고변동성 주식들보다 높은 수익률을 가져온다. 이런 저변동성 주식에 대한 현상들의 원인에 대해서 여러가지 주장들이 있다. 예를 들면, 투자자들의 자신에 대한 과잉 믿음이라든가, 혹은 변동성 지표 그 자체에 대한 한계점이라든가등등 여러가지가 존재한다. 하지만 이 포스트에서는 그런 원인에 대한 이야기는 나중에 쓰고, 일단 저변동성 현상이 우리에게 좋은 주식을 알려줄 수 있는 사실이니 그것에만 주목하도록하자. 사실 저변동성 현상을 실증적으로 분석하려면, 어느정도의 벡테스팅이 수반되어야한다. 그것까지 설명하려면 포스트가 길어지므로 생략하겠다.
# 주가 수익률 계산
ret <- Return.calculate(price_list,method = c("log"))
# 변동성 계산
f_s <- function(x){ # NA 값들 처리를 위한 함수
sd(x,na.rm = T)
}
# 일간 변동성
std_daily <- apply(ret,2,f_s) %>% multiply_by(sqrt(252))
# 주간 변동성
std_week <- ret %>% apply.weekly(Return.cumulative) %>% apply(.,2,f_s) %>% multiply_by(sqrt(52))
1. 지난 포스트에 Return.calculate의 method 파라미터를 몰라서 로그 수익률을 따로 계산했었는데, 사실 있었다.
2. 위에서 언급한 NA들을 처리하기위해 sd 함수의 na.rm 파라미터를 사용한다. na.rm = T로 설정하면, 적용되는 데이터의 NA 값들을 다 삭제한다.
3. apply 함수를 써서 주가 수익률의 표준편차를 구한다. 그 다음에 영업일 제곱근 252을 곱해주면 일별 변동률을 구할 수 있다.
4. 참고로, 변동성 분석에 주가가 아니라 수익률을 쓰는 이유는, 주가를 쓰면 주가가 큰 주식은 자연히 변동성이 높게, 구가가 낮은 주식은 변동성이 낮게 측정이 되기 때문이다. 그래서 수익률로 "정규화"를 해준다.
# 일간 변동성 순위 : 상위 3개
std_daily[rank(std_daily) <= 3]
'''
X601318 X601166 X600519
0.2885881 0.2827725 0.2986697
'''
# 일간 변동성 순위 : 하위 3개
std_daily[rank(std_daily) >= 21]
'''
X600371 X603613 X600707
0.6584834 0.7438657 0.6389261
'''
# 주간 변동성 순위 : 상위 3개
std_week[rank(std_week) <= 3]
'''
X600908 X600190 X600519
0.2879637 0.2544286 0.2744505
'''
# 주간 변동성 순위 : 하위 3개
std_week[rank(std_week) >= 21]
'''
X603613 X603025 X600707
0.7737482 0.8747297 0.6637084
'''
1. 첫번째 코드는 일간 변동성이 제일 작은 3 종목이다. 왼쪽에서 오른쪽 순서로, 평안보험(平安保险),흥업은행(兴业银行),귀주모태(贵州茅台)이다.
2. 두번째 코드는 일간 변동성이 제일 큰 3 종목이다. 순서는 위와 동일하며, 차례대로 만향덕농(万向德农),국연주식(国联股份),무지개주식(彩虹股份)이다.
3. 세번쨰 코드는 주간 변동성이 제일 작은 3종목이다. 순서는 위와 동일하고, 차례대로 우시은행(无锡银行),금주항(锦州港),귀주모태(贵州茅台)이다
4. 네번째 코드는 주간 변동성이 제일 큰 3 종목이다. 순서는 위와 동일하며, 차례대로 국연주식(国联股份),대호과기(大豪科技),무지개주식(彩虹股份)이다.
5. 일간, 주간 변동성 둘 다 상위 3개에 드는 종목은 귀주모태이고, 둘 다 하위 3개 안에 드는 종목은 국연주식과 무지개 주식이다.
6. 주의할 점은 , R은 기본적으로 오름차순 구조를 택하므로 rank함수의 높은 순위는 낮은 변동성을 의미한다.
저변동성 효과,일명 로우볼(Low Vol) 효과를 간단하게 살펴봤는데, 그래서 무조건 저변동성 주식을 사면 돈을 벌 수 있나라고 물으면 절대로 아니다. 투자는 다양한 지표를 보면서 결정해야하고 한가지 지표만 보면서 포트폴리오를 구성하는 건 매우 위험한 행동이라고 생각한다. 예를 들면, 변동성 지표는 매우 좋은 지표들 중 하나이지만, 그 지표가 모든 걸 반영해주지는 못한다. 변동성이 낮지만 계속해서 주가가 하락하는 모멘텀인 주식도 분명히 있을 것이다. 이런 주식의 경우에는 변동성 지표만 보고 투자하면 돈을 잃을 확률이 매우 높다. 그리고 이렇게 주식 종목의 표본도 적으면 안된다.
참고문헌
[1]R을 이용한 퀀트 투자 포트폴리오 만들기.이현열
'투자를 위한 코딩 > R' 카테고리의 다른 글
R로 중국 주식 티커 데이터 가져오기 (0) | 2021.01.18 |
---|---|
R로 네이버 주식 데이터 크롤링 (0) | 2021.01.11 |
R로 주식 데이터 가져오기 & 베타 분석 (0) | 2020.12.09 |
다변량 통계분석과 R 모델링(多元统计分析及R语言建模) (1) (0) | 2020.11.29 |
R에서의 이상치 탐지/제거 방법 (0) | 2020.11.28 |
댓글