Post List

2020년 3월 26일 목요일

지난해 $ 1Billion (1조) 이상을 번 5명의 헤지펀드 수장들



블룸버그 추정기준 작년 1조 이상을 번 5명의 헤지펀드 매니저를 소개합니다!!! (헤지펀드의 2%-20% 를 가정하여 산출했다고 합니다.)


크리스 혼에 대한 이미지 검색결과

1위: 크리스 혼 (TCI Fund Management)
수익: 2조 2,619억



짐 사이먼스에 대한 이미지 검색결과

2위: 짐 사이먼스 (Renaissance Technologies)
수익: 2조 1,209억

퀀트의 전설 짐 사이먼 형님은 아쉽게 2등..





켄 그리핀에 대한 이미지 검색결과

3위: 켄 그리핀 (Citadel)
수익: 1조 8,390억


스티브 코헨에 대한 이미지 검색결과

4위: 스티브 코헨 (Point72 Asset Management)
수익: 1조 5,447억


chase coleman tiger global에 대한 이미지 검색결과

5위: 체이스 콜먼 (Tiger Global Management)
수익: 1조 3,547억



2020년 3월 23일 월요일

파도가 심할때는 잠시 쉬어갑시다


투자는 무엇일까요? 어떤 면에서 저는 수익률이라는 보물을 찾아 먼 바다를 항해하는 것이 아닌가 합니다. 재무제표라는 나침반과, 바람이라는 주가 모멘텀, 배라는 기업을 가지고요.

바다의 바람이 평소와 같으면 항해도 평소와 같이 하면 됩니다. 믿을 만한 나침반(재무제표)을 가지고 바람(모멘텀)을 타고 앞으로 나아가기만 하면 되죠. 배가 튼튼할 수록 더 좋겠죠.

그러나 안개가 가득끼고 폭풍우가 몰아치면 아무리 좋은 나침반도, 배도 쓸모가 없습니다. 어설프게 자연에 맞서려다가는 침몰하는게 눈에 뻔합니다. 이럴때는 무리해서 배를 끌고나가기 보다 바람이 잦아들때까지 배를 정박하고 기다리는게 좋지 않을까요? 어짜피 그 보물은 누가 먼저 가져간다고 내 것이 사라지는 것도 아니니까요. 살아야 보물도 찾죠.

2020년 3월 14일 토요일

유동성 소용돌이: 하락은 어떻게 하락을 부추기는가?



여러분이 생각하는 것 보다 시장은 복잡한 형태의 함수로 이루어져 있습니다. 각종 파생상품, 레버리지, 대차거래, 신용매매 등이 여기에 해당합니다.

헤지펀드의 경우 100% 매수 포지션을 위해 자기돈 100%를 내는 경우는 거의 없습니다. 자기돈의 50%로 주식을 매수하면 나머지 50%는 프라임 브로커에서 이 주식들을 담보로 제공해준 자금을 통해 매수하면 됩니다. 이것이 흔히 알고있는 '레버리지' 이죠. 개인들의 신용미수 역시 이와 동일하다고 보면 됩니다.

정상적인 시장에서 이러한 메커니즘은 아무 문제가 없겠죠. 그러나 시장이 급락하고 공포에 휩싸여, 모두가 탈출하고 싶어할 때는 얘기가 다릅니다.




먼저 시장에 엄청난 쇼크가 발생한다고 생각합시다. 먼저 쇼크로 인한 손실때문에 트레이더는 포지션을 줄여야 합니다. 그러나 더 큰 문제는 프라임브로커와의 자금 조달 관계입니다. 위험을 느낀 프라임 브로커들이 더욱 많은 담보를 요구하거나, 혹은 부도위험, 손실관리 등 각종 위험관리를 위해 더이상 자금제공을 하지 않기도 합니다.

결국 트레이더는 애초에 매도해야했던 금액에 더해 더 큰 매도를 해야하며, 이는 엄청난 매도 압력을 만들어냅니다. 그 결과 동일한 포지션을 가진 모든 트레이더는 더 큰 손해를 보게 됩니다.


시장의 매도 압력이 커지면 어떻게 될까요? 이러한 주문 불균형으로 인해 시장 변동은 더 커지고 유동성은 말라붙게 됩니다. 일반적인 상황에서 이런 트레이더들이 유동성 공급자였는데, 이제 그들은 매도만을 위해 달려가고 있으니까요.

악순환은 다시 돌아옵니다. 변동성이 높아지고 유동성이 하락함에  따라 위험을 느낌 프라임브로커는 또다시 증거금을 올리고, 트레이더가 포지션을 정리하도록 만듭니다. 트레이더역시 손실로 인해 포지션을 추가로 줄여야 하며, 투자자나 경영자들이 돈을 뺴가기도 합니다. 이런 악순환은 계속 반복됩니다. 이러한 일련의 과정을 유동성 소용돌이 라고 합니다.



이 상황은 언제 멈출까요? 바로 디레버리징이 끝날때 입니다. 모든 복잡한 함수가 정리되면 가격은 바닥에 도달합니다. 그 후 일부 트레이더는 다시 레버리지를 사용하여 매수를 하기 시작하면 가격은 내재가치를 향해 반등하기 시작합니다. 이 내재가치는 기존 보다 하락한 지점에서 평형을 이루게 됩니다.

물론 그 반등이 몇일만에 끝날지, 아니면 몇달이나 걸릴지는 아무도 모르지겠지만요... (논문에서는 알수 있다고 하니 우리모두 공부합시다!)



참고문헌: Pedersen, Lasse Heje. When everyone runs for the exit. No. w15297. National Bureau of Economic Research, 2009.

2019년 11월 15일 금요일

dtplyr: dplyr의 편리함과 data.table의 속도를 그대로!



R 내에서 데이터 처리시 가장 많이 사용되는 패키지는 dplyr 입니다. 개인적으로 dplyr을 쓰지 않는 다는 것은 R의 1%도 활용하지 못한다는 생각이 들 정도입니다. 그러나 데이터 양이 늘어날수록 속도가 느려지는 것은 당연합니다.

물론 data.table 패키지라는 해결책도 있습니다. R 뿐만 아니라 다른 언어들과 비교해도 속도에서 만큼은 깡패급으로 빠릅니다. 그러나 사용하기에 문법이 너무 복잡해 접근이 쉽지 않습니다.

(아무리 외워도 외워지지 않는 그놈의 DT[i, j, by].....)


이런 문제를 해결하기 위해 Rstudio에서 새로운 해법을 내놓았습니다. dplyr 문법을 입력하면 data.table 명령어로 래핑하여 데이터를 처리하는, 즉 dplyr의 편리함과 data.table의 속도를 둘 다 잡은 dtplyr 패키지를 선보였습니다.


두말할 필요없이 속도 비교를 해보도록 합시다.

library(nycflights13)
library(data.table)
library(dtplyr)
library(dplyr, warn.conflicts = FALSE)
library(microbenchmark)
library(ggplot2)

먼저 필요한 패키지들을 불러옵니다. dplyr의 경우 warn.conflicts() 인자를 FALSE로 두어 충돌 메세지를 끄도록 합니다.
CRAN 버젼 dtplyr 설치시 에러가 날 수 있습니다. 이는 연결 패키지인 data.table이 최근에 업데이트 되어서이며, data.table을 제거 후 인스톨 한후 dtplyr을 설치하면 잘 설치 됩니다.
# load data
df = flights
dim(df)
## [1] 336776     19
head(df)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##                            
## 1  2013     1     1      517            515         2      830
## 2  2013     1     1      533            529         4      850
## 3  2013     1     1      542            540         2      923
## 4  2013     1     1      544            545        -1     1004
## 5  2013     1     1      554            600        -6      812
## 6  2013     1     1      554            558        -4      740
## # ... with 12 more variables: sched_arr_time , arr_delay ,
## #   carrier , flight , tailnum , origin , dest ,
## #   air_time , distance , hour , minute ,
## #   time_hour 
필요한 flight 데이터를 불러오도록 합니다. 총 336776개의 행으로 구성되어 있습니다.

df_dt = data.table(df)
df_tb = as_tibble(df)
df_lz = lazy_dt(df)
데이터 테이블을 각 형식에 맞게 바꿔주도록 합니다. data.table() 함수를 이용해 데이터테이블 형태로, as_tibble() 함수를 이용해 티블 형태로, 마지막으로 dtplyr 패키지의 lazy_dt() 함수를 이용해 dtplyr_step_first 형태로 바꾸어 줍니다. 해당 형식을 통해 dplyr 명령어를 data.table 명령어로 래핑하여 데이터를 처리하게 됩니다.

summary(df_lz)
##               Length Class      Mode       
## parent        19     data.table list       
## vars          19     -none-     character  
## groups         0     -none-     character  
## implicit_copy  1     -none-     logical    
## needs_copy     1     -none-     logical    
## env            5     -none-     environment
## name           1     -none-     name
summary()를 통해 확인해보면 data.table 클래스에 굉장히 독특한 형태로 생겼습니다.
이제 본격적으로 속도를 비교해보도록 하겠습니다. 먼저 필터에 대한 속도 비교입니다.

dtply을 이용할 경우 lazy_dt()를 통해 변경된 데이터에 기존 dplyr 명령어를 그대로 사용해서 데이터처리를 하며, as_tibble() 을 통해 티블 형태로 변경해주길 권장합니다. (그렇지 않으면 용량이 매우 큰 dtplyr_step_first 형태로 남아있게 됩니다.)
results = microbenchmark(
  `data.table` = df_dt[origin == 'JFK' & carrier == 'AA'] ,
  `dplyr` = df_tb %>% filter(origin == 'JFK' & carrier == 'AA'),
  `dtplyr` = df_lz %>% filter(origin == 'JFK' & carrier == 'AA') %>% as_tibble(),
  times = 100
)

results
## Unit: milliseconds
##        expr    min      lq      mean   median       uq     max neval cld
##  data.table 5.2220 6.92075  7.842533  7.49525  8.31910 19.4979   100 a  
##       dplyr 8.1038 9.33090 11.130208 10.14395 11.50605 27.7445   100   c
##      dtplyr 6.5998 8.35225  8.902763  8.83630  9.42860 11.7533   100  b
autoplot(results) +
  aes(fill = expr) +
  theme_bw() +
  labs(title = "Filter")

data.table이 압도적으로 빠르며, 그뒤가 dtplyr, dplyr 순입니다. 물론 data.table과 dtplyr은 간혹 속도가 매우 오래 걸리는 경우가 있어 mean 값이 증가하는 경우도 있습니다.
그러나 data.table와 dtplyr 진정한 위력은 매우 복잡한 데이터를 처리하는데 있습니다. 이번에는 그룹화, 통계값 계산, 필터링 이라는 복잡한 데이터처리를 해보도록 하겠습니다.

results2 = microbenchmark(

  `data.table` = df_dt[, .(mean_delay = mean(dep_delay, na.rm = TRUE)),
        by = c('year', 'month', 'day', 'carrier', 'origin')][mean_delay >= 10],

  `dplyr` = df_tb %>%
    group_by(year, month, day, carrier, origin) %>%
    summarize(mean_delay = mean(dep_delay, na.rm = TRUE)) %>%
    ungroup() %>%
    filter(mean_delay >= 10),

  `dtplyr` = df_lz %>%
    group_by(year, month, day, carrier, origin) %>%
    summarize(mean_delay = mean(dep_delay, na.rm = TRUE)) %>%
    ungroup() %>%
    filter(mean_delay >= 10) %>%
    as_tibble(),

  times = 100
)

results2
## Unit: milliseconds
##        expr     min       lq     mean   median       uq      max neval cld
##  data.table 14.1369 16.05185 17.85918 16.82690 18.81835  25.7218   100 a  
##       dplyr 71.1181 81.33950 91.00665 90.43740 99.06195 130.1543   100   c
##      dtplyr 14.9752 19.12145 22.18545 20.24325 21.60995 148.0419   100  b
autoplot(results2) +
  aes(fill = expr) +
  theme_bw() +
  labs(title = "group_by, mean, filter")


data.table과 dtply이 dplyr에 비해 압도적인 속도 차이를 보입니다. 이러한 차이는 데이터 크기가 늘어날수록, 그리고 데이터 처리 구조가 복잡해 질수록 늘어나게 됩니다.
이처럼 dtplyr은 기존 dplyr 문법을 활용하여 data.table과 거의 비슷한 성능을 내는 보물같은 패키지입니다.

2019년 9월 17일 화요일

RSelenium (셀레니움)을 이용한 동적 웹페이지 크롤링하기





'R을 이용한 퀀트 투자 포트폴리오 만들기'가 출간 되었습니다.
많은 관심 부탁드리겠습니다.

↓↓ 구매링크 ↓↓



일반적인 (정적) 웹페이지는 서버에 미리 저장된 파일을 그대로 웹페이지에 전달하므로, 손쉽게 크롤링 할 수 있습니다.


반면 동적 웹페이지는 서버에 있는 데이터들이 스크립트에 의해 가공처리한 후 생성되어 웹 페이지에 전달되므로, 웹페이지가 계속해서 바뀌게 됩니다. 따라서 일반적을 방법으로는 크롤링할 수 없습니다.

dynamic-web.png


네이버의 요약 재무제표 항목 역시 이러한 동적 웹페이지로 이루어져 있어 기존 방식으로 크롤링 하려면 매우 번거롭지만, 셀레니움을 이용할 경우 손쉽게 크롤링할 수 있습니다.

Selenium은 주로 웹앱을 테스트하는데 이용하는 프레임워크로써, webdriver라는 API를 통해 운영체제에 설치된 Chrome등의 브라우저를 제어합니다.

브라우저를 직접 동작시킨다는 것은 JavaScript를 이용해 비동기적으로 혹은 뒤늦게 불러와지는 컨텐츠들을 가져올 수 있다는 것입니다. 즉, ‘눈에 보이는’ 컨텐츠라면 모두 가져올 수 있으며, JavaScript로 렌더링이 완료된 후의 DOM결과물에 접근이 가능합니다.

(사실 몰라도 됩니다...)

먼저 아래 3개 파일을 본인의 OS에 맞게 다운로드 받은 후, 하나의 폴더에 저장해줍니다. geckodriver chromeDriver는 압축을 해제하며, selenium은 그대로 둡니다.

chromeDriver의 경우 본인이 사용하고 있는 크롬 버젼과 같은 것을 다운로드해야 하며, 해당 버젼의 확인은 크롬창에서 [도움말] -> [Chrome 정보]를 통해 확인할 수 있습니다.







윈도우에서 cmd를 통해 명령 프롬프트를 연 후, 아래 명령어를 입력합니다.

C:\Rselenium에는 파일은 다운로드 받은 폴더의 위치를 입력하며, standalone 뒤 3.141.59는 본인이 다운로드 받은 selenium-server-standalone 파일의 버젼과 같은 숫자를 입력합니다.

해당 cmd 창은 계속 열어두어야 합니다.

cd C:\Rselenium
java -Dwebdriver.gecko.driver="geckodriver.exe" -jar selenium-server-standalone-3.141.59.jar -port 4445



install.packages('RSelenium')
install.packages('seleniumPipes')
library(RSelenium)
library(seleniumPipes)
library(rvest)
library(httr)

셀레니움 관련 패키지인 RSelenium와 seleniumPipes를 인스톨 한 후, 관련 패키지들을 열어줍니다.



remDr = remoteDriver(
  remoteServerAddr="localhost",
  port=4445L,
  browserName="chrome")

remDr$open()
## [1] "Connecting to remote server"
## $acceptInsecureCerts
## [1] FALSE
## 
## $browserName
## [1] "chrome"
## 
## $browserVersion
## [1] "77.0.3865.75"
## 
## $chrome
## $chrome$chromedriverVersion
## [1] "76.0.3809.126 (d80a294506b4c9d18015e755cee48f953ddc3f2f-refs/branch-heads/3809@{#1024})"
## 
## $chrome$userDataDir
## [1] "C:\\Users\\Henry\\AppData\\Local\\Temp\\scoped_dir11848_77181124"
## 
## 
## $`goog:chromeOptions`
## $`goog:chromeOptions`$debuggerAddress
## [1] "localhost:54111"
## 
## 
## $networkConnectionEnabled
## [1] FALSE
## 
## $pageLoadStrategy
## [1] "normal"
## 
## $platformName
## [1] "windows nt"
## 
## $proxy
## named list()
## 
## $setWindowRect
## [1] TRUE
## 
## $strictFileInteractability
## [1] FALSE
## 
## $timeouts
## $timeouts$implicit
## [1] 0
## 
## $timeouts$pageLoad
## [1] 300000
## 
## $timeouts$script
## [1] 30000
## 
## 
## $unhandledPromptBehavior
## [1] "dismiss and notify"
## 
## $webdriver.remote.sessionid
## [1] "6de07eeb6dfc9fd8203ce2708342ca05"
## 
## $id
## [1] "6de07eeb6dfc9fd8203ce2708342ca05"
remDr$navigate('https://finance.naver.com/item/coinfo.nhn?code=005930&target=finsum_more')

먼저 remoteDriver() 함수를 통해 4445번 포트와 크롬을 연결시켜 주며, remDr$open() 함수를 입력하면 크롬 웹창이 열리게 됩니다. remDr$navigate() 함수 내부에 크롤링하고자 하는 사이트 주소를 입력하면 해당 주소로 이동하게 됩니다.

현재 페이지가 셀레니움에 의해 동작되므로, 자동화된 테스트 소프트웨어에 의해 제어되고 있습니다. 라는 문구가 뜹니다.





이제 Financial Summary에 해당하는 부분을 찾아나갑니다.





해당 페이지는 iframe 내부에서 javascript를 통해 타 페이지의 데이터가 들어와있는 형태이므로, 해당 iframe 내부로 접근해야 합니다.


# ID 찾아내기
frames = remDr$findElements(using = "id",
                  value = 'coinfo_cp')

print(frames)
## [[1]]
## [1] "remoteDriver fields"
## $remoteServerAddr
## [1] "localhost"
## 
## $port
## [1] 4445
## 
## $browserName
## [1] "chrome"
## 
## $version
## [1] ""
## 
## $platform
## [1] "ANY"
## 
## $javascript
## [1] TRUE
## 
## $nativeEvents
## [1] TRUE
## 
## $extraCapabilities
## list()
## 
## [1] "webElement fields"
## $elementId
## [1] "f0bfdfff-3c16-4331-ad30-6df5436064cd"
# Frame 안으로 접근
remDr$switchToFrame(frames[[1]])


remDr$findElements() 함수는 원하는 요소로 접근이 가능합니다. 위 iframe은 coinfo_cp라는 id를 사용하고 있으므로 using에는 id를, value에는 coinfo_cp를 입력하며, 이 외에도 xpath, css selector 등 다양한 html 태그를 통해 원하는 html 정보를 찾을 수 있습니다.
그 후 remDr$switchToFrame() 함수를 통해 iframe 내부로 접근합니다.
우리가 필요한 데이터는 연간 재무제표 이므로, [연간]에 해당하는 탭의 위치를 찾은 후 Xpath를 복사합니다. 해당 위치는 다음과 같습니다.
//*[@id="cns_Tab21"]




# 연간 클릭
remDr$findElement(using = 'xpath',
                  value ='//*[@id="cns_Tab21"]')$clickElement()
remDr$findElement() 함수 내부에 xpath를 사용하여 해당 위치를 찾은 후, $clickElement()를 입력하면 해당 위치를 클릭하게 됩니다. 이제 연간 재무제표가 표시된 페이지를 읽어오도록 하겠습니다.




page_parse = remDr$getPageSource()[[1]]
page_html = page_parse %>% read_html()
  1. remDr$getPageSource() 함수를 통해 페이지 소스를 읽어옵니다.
  2. read_html() 함수를 통해 HTML 정보만을 읽어옵니다. 이제 재무제표 데이터가 들어있는 테이블만 추출하면 됩니다.

Sys.setlocale('LC_ALL', 'English')
table = page_html %>% html_table(fill = TRUE)
Sys.setlocale('LC_ALL', 'Korean')
df = table[[13]]
head(df)

##         주요재무정보                                  연간
## 1       주요재무정보 2014/12\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2             매출액                             2,062,060
## 3           영업이익                               250,251
## 4 영업이익(발표기준)                               250,251
## 5   세전계속사업이익                               278,750
## 6         당기순이익                               233,944
##                                    연간
## 1 2015/12\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2                             2,006,535
## 3                               264,134
## 4                               264,134
## 5                               259,610
## 6                               190,601
##                                    연간
## 1 2016/12\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2                             2,018,667
## 3                               292,407
## 4                               292,407
## 5                               307,137
## 6                               227,261
##                                    연간
## 1 2017/12\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2                             2,395,754
## 3                               536,450
## 4                               536,450
## 5                               561,960
## 6                               421,867
##                                    연간
## 1 2018/12\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2                             2,437,714
## 3                               588,867
## 4                               588,867
## 5                               611,600
## 6                               443,449
##                                       연간
## 1 2019/12(E)\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2                                2,305,363
## 3                                  268,387
## 4                                         
## 5                                  294,636
## 6                                  215,948
##                                       연간
## 1 2020/12(E)\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2                                2,469,186
## 3                                  354,097
## 4                                         
## 5                                  381,833
## 6                                  279,894
##                                       연간
## 1 2021/12(E)\n\t\t\t\t\t\t\t\t\t(IFRS연결)
## 2                                2,648,131
## 3                                  446,023
## 4                                         
## 5                                  480,005
## 6                                  352,816

  1. 로케일 언어를 English로 변경합니다.
  2. html_table() 함수를 통해 편하게 테이블 데이터만 추출할 수 있습니다.
  3. 다시 로케일 언어를 Korean으로 변경합니다.
  4. 총 19개 테이블 중 13번째 테이블이 연간 재무제표에 해당합니다. 만일 다른 테이블을 추출하고자 하면 해당 숫자만 변경해주면 됩니다.

우리가 원하는 연간 재무제표 데이터가 제대로 크롤링 되었으며, 간단한 클렌징 작업만 거치면 모든 작업이 완료됩니다.

library(stringr)
library(magrittr)

rownames(df) = df[, 1]
df = df[, -1]

colnames(df) = df[1, ]
df = df[-1, ]
colnames(df) = str_sub(colnames(df), 1, 7)

df = sapply(df, function(x) {
  str_replace_all(x, ',', '') %>%
    as.numeric()
}) %>%
  data.frame(., row.names = rownames(df))

head(df)
##                    X2014.12 X2015.12 X2016.12 X2017.12 X2018.12 X2019.12
## 매출액              2062060  2006535  2018667  2395754  2437714  2305363
## 영업이익             250251   264134   292407   536450   588867   268387
## 영업이익(발표기준)   250251   264134   292407   536450   588867       NA
## 세전계속사업이익     278750   259610   307137   561960   611600   294636
## 당기순이익           233944   190601   227261   421867   443449   215948
## 당기순이익(지배)     230825   186946   224157   413446   438909   213435
##                    X2020.12 X2021.12
## 매출액              2469186  2648131
## 영업이익             354097   446023
## 영업이익(발표기준)       NA       NA
## 세전계속사업이익     381833   480005
## 당기순이익           279894   352816
## 당기순이익(지배)     275918   347589

  1. 첫번째 열을 행이름으로 설정한 후, 해당 열을 삭제합니다.
  2. 첫번재 행을 열이름으로 설정한 후, 해당 행을 삭제합니다.
  3. 열이름에서 str_sub() 함수를 이용해 1~7번째 글자만 선택합니다. (YYYY/MM)
  4. sapply() 함수 내에서 str_replace_all() 함수를 이용해 모든 콤마(,)를 없앤 후 숫자 형태로 변경합니다.

결과물을 출력해보면 클렌징 작업이 완료되었습니다.