[Project] 증권사 리포트에 따라 사면 어떻게 될까??

Quant|2022. 3. 11. 13:50
반응형


Question

문뜩 증권사 애널리스트 말대로 사면 좋은 수익을 낼 수 있는지 의문이 들었다.

애널리스트는 직업으로 기업 분석에 모든 시간을 투자하기 때문에 개인 투자자보다 

분석에 우위를 가지고 있을 것이기 때문이다.


Design

Where & Who

어떤 증권사의 애널리스트들을 선정했는지는 해당 증권사, 애널리스트 이미지에

오해의 소지를 가져올 수 있으므로 blind 처리하였다.

 

How

증권사 홈페이지에 게시된 리포트 종목/날짜/평가등급을 크롤링 후

dataframe으로 변경 후, 리포트 게시 날의 종목 가격, 일정 기간 후의 가격, 변화율 특성을 추가한다.

 

When 

2019년 1월 1일 ~ 2022년 1월 1일까지 게시된 리포트를 구하고,

3개월 이후의 가격과 비교하도록 한다.

 

What

기간 내 10개 미만의 리포트를 쓴 애널리스트는 제외시킨다.


Develop

1) 애널리스트 이름과 각 애널리스트들의 리포트를 내역을 저장할 dataframe list 생성

name_list = ['이**' ,
'최**' ,
'박**' ,
'이**' ,
'이**' ,
'박**' ,
'안**' ,
'김**' ,
'정**' ,
'이**' ,
'김**' ,
'백**' ,
'허**' ]

analysis_list = []

2) 종목 가격을 찾는 API를 사용하려면 종목코드로 검색해야 하기 때문에 종목명 : 종목코드 dictionary 생성

stock_list = pd.DataFrame({'종목코드':stock.get_market_ticker_list(market="ALL")}) 
stock_list['종목명'] = stock_list['종목코드'].map(lambda x: stock.get_market_ticker_name(x))
stock_list = stock_list.set_index('종목명').T.to_dict('list')

3) 리포트 데이터들 크롤링

def analysis_crawling(name) :

  # site page 별로 데이터 크롤링 
  df_list = []
  
  # 애널리스트별로 page 수가 다르기 때문에 page 수를 16으로 설정
  page_num = 16

  # page 수만큼 다 크롤링해서 중간에 끊기는 경우 braek로 크롤링 중단
  exit1 = 0
  exit2 = 0

  for i in range(page_num):
    
    if exit2 == 1:
      break

    # 해당 url로 이동 , 19년 1월 1일 ~ 22년 1월 1일
    url = "url 주소는 blind"
    driver.implicitly_wait(10)
    driver.get(url)
    driver.implicitly_wait(10)
    
    # table 형태의 애널리스트 리포트 데이터 추출
    table = driver.find_element_by_class_name() # blind
    tbody = table.find_element_by_tag_name("tbody")
    rows = tbody.find_elements_by_tag_name("tr")

    df = pd.DataFrame()

    for k in range(3):
      if exit1 == 1:
        exit2 == 1
        break

      content = []
      
      for index, value in enumerate(rows):
          body=value.find_elements_by_tag_name("td")[k]
          content.append(body.text)

	  # page 끝난 경우
      if k == 0 :
        if body.text == '조회 자료가 없습니다.':
          exit1 = 1
          break
        # 날짜 데이터
        else :
          df['date'] = content
      elif k == 1 : # 종목 데이터
        df['stock'] = content
      elif k == 2 : # 평가 등급 데이터
        df['rate'] = content
    
    # page 별로 만들어진 dataframe list에 추가 
    df_list.append(df)
  
  # df_list 에 있는 page 별 report들 통합
  analysis = pd.concat(df_list)
  # 과거 -> 현재 순으로 정렬
  analysis = analysis[::-1]
  # 날짜 형식 변경
  analysis["date"] = analysis["date"].str.replace(pat=r'[^\w]', repl=r'', regex=True)


  return analysis

4) 날짜에 따른 가격 데이터 열 생성

def price_search(analysis, name) :
  re_price = []
  af_price = []

  for i in range(analysis.shape[0]) :  
    
    # 종목 코드
    c = stock_list.get(analysis['stock'].iloc[i]) 
    
    if c == None :
      re_price.append(0)
      af_price.append(0)
      
      continue 
    else :
      c = c[0]

    # 리포트 발표 날짜 
    re_date = analysis['date'].iloc[i]
    
    # 주말 , 공휴일, 연휴 고려 탐색기간 5일
    re_end = datetime.datetime.strptime(re_date, '%Y%m%d') + datetime.timedelta(hours=96)  
    re_end_str = re_end.strftime('%Y%m%d')
    
    # 발표 날 주가 데이터
    re_p = stock.get_market_ohlcv(re_date, re_end_str, c)
    
    # 주가가 없으면 0원 처리
    if re_p.shape[0] == 0 :
      re_e = 0
    else :
      re_e = re_p['종가'].iloc[0]

    # 3개월 후 주가 
    af_date = datetime.datetime.strptime(re_date, '%Y%m%d') + datetime.timedelta(weeks=12)
    af_end = af_date + datetime.timedelta(hours=120)  
    
    # datetime -> str
    af_date_str = af_date.strftime('%Y%m%d')
    af_end_str = af_end.strftime('%Y%m%d')
    
    # 발표 날 주가 데이터
    af_p = stock.get_market_ohlcv(af_date_str, af_end_str, c)
    
    # 주가가 없으면 0원 처리
    if af_p.shape[0] == 0 :
      af_e = 0
    else :
      af_e = af_p['종가'].iloc[0]


    re_price.append(re_e)
    af_price.append(af_e)
    
  # 리포트 등록 날 주가, 3개월 후 주가 특성 추가
  analysis['report_price'] = re_price
  analysis['month_price'] = af_price
  
  # 꼬인 index 재정렬
  analysis.reset_index(inplace=True, drop=True)

  # 주가 열이 0인 경우 해당 행 삭제
  indexNames = analysis[ (analysis['report_price'] == 0)
                  | (analysis['month_price'] == 0) ].index
  analysis.drop(indexNames , inplace=True)
  
  # 3개월 변화율 특성 추가
  analysis['change'] = 100 * (analysis['month_price'] - analysis['report_price']) / analysis['report_price']

  # 오른 종목 수 counting
  u = 0

  for i in range(analysis.shape[0]):
    if analysis['change'].iloc[i] > 0 :
      u += 1
  
  # report 수 대비 오른 종목 수로 점수화 
  print("Analysis score is  : " , round(u /analysis.shape[0],3))

  return analysis

Evaluation

analysis score가 가장 높은 사람의 점수는 0.635이고, 가장 낮은 사람의 점수는 0.136이다.

 

name : 애널리스트 이름

outperform : 리포트 중 가장 많이 오른 종목

max ratio : 가장 많이 오른 종목의 3개월 변화율

max ratio : 가장 많이 내린 종목의 3개월 변화율

mean propit ratio : 평균 수익률

analysis score : 등록한 report  대비 상승한 종목 비율

 

name : 이**
outperform : 카카오 
max ratio : 66.75%
min profit ratio : -49.041% 
mean propit ratio : 3.958%
analysis score is : 0.571
number of report : 126

name : 최**
outperform : 두산
max ratio : 57.48%
min profit ratio : -30.036% 
mean propit ratio : -0.133%
analysis score is : 0.437
number of report : 119

name : 박**
outperform : 상아프론테크
max ratio : 155.446%
min profit ratio : -50.76% 
mean propit ratio : 3.195% 
analysis score is : 0.455
number of report : 156

name : 이**
outperform : LG전자
max ratio : 108.187%
min profit ratio : -29.924% 
mean propit ratio : 9.511%
analysis score is : 0.635
number of report : 115

name : 이**
outperform : 세아베스틸
max ratio : 97.77%
min profit ratio : -40.809%
mean propit ratio : 2.034%
analysis score is : 0.481
number of report : 135

name : 박**
outperform : 신세계인터내셔날
max ratio : 40.751%
min profit ratio : -36.961%
mean propit ratio : -2.361% 
analysis score is : 0.409
number of report : 225

name : 안**
outperform : 서울옥션
max ratio : 101.863%
min profit ratio : -39.627%
mean propit ratio : 4.022% 
analysis score is : 0.494
number of report : 85

name : 김** 
outperform : 동화기업
max ratio : 113.58%
min profit ratio : -42.105% 
mean propit ratio : 2.762%
analysis score is : 0.465
number of report : 172

name : 정** 
outperform : 한국금융지주
max ratio : 51.515%
min profit ratio : -33.381% 
mean propit ratio : 3.65% 
analysis score is : 0.574
number of report : 289

name : 이**
outperform : 카페24
max ratio : 45.206%
min profit ratio : -52.922% 
mean propit ratio : -1.786%
analysis score is : 0.399
number of report : 143

name : 김**
outperform : 에코프로비엠
max ratio : 111.623%
min profit ratio : -21.259%
mean propit ratio : 11.043%
analysis score is : 0.595
number of report : 84

Name : 허**
outperform : 아이엘사이언스
max ratio : 37.072%
min profit ratio : -36.853% 
mean propit ratio : -12.564% 
analysis score is : 0.136
number of report : 22

 

Feedback

사실은 각 애널리스트 저마다 올리는 리포트의 섹터가 다르다 

score가 높은 사람은 해당 기간 동안 해당 섹터가 선방했기 때문에 점수가 높은 것이고,

score가 낮은 사람은 그 반대이다.

 

또한 대부분의 애널리스트의 점수가 0.5 근처인 것을 보았을 때,

내부자 정보를 알지 않는 이상 아무리 분석을 많이 한 사람이라도 주가의 향방 타이밍은 알 수가 없다.

애널리스트는 기업의 내제 가치를 평가하는 사람일 뿐 기업의 미래를 아는 사람은 아니다.

 

 

 

 

피터 린치의 명언으로 마무리

 

 

반응형

'Quant' 카테고리의 다른 글

Finance 참고 블로그  (26) 2022.03.20
[Quant Strategy] NCAV 전략 - 2022.03.16 기준  (11) 2022.03.17
[Quant Strategy] Pair Trading with Long/Short  (17) 2022.02.20
Project : Price and EPS NTM  (0) 2021.12.09
[금융상품] 채권  (18) 2021.10.26

댓글()