본문 바로가기

코딩/퀀트 투자

[파이썬/ 웹 스크래핑] 벤저민 그레이엄의 투자 전략에 따른 종목 선정 - 방어 투자자(1)

1. 목표

 

미국 주식을 대상으로 벤저민 그레이엄의 투자 전략에 따라 종목 선정을 하는 것이다.

 

투자 전략은 <현명한 투자자>라는 책의 내용을 토대로 설정했으며, 투자 성향에 따라 방어 투자자, 공격 투자자로 분리하여 투자 전략을 설정해 종목을 선정했다.

 

스티크 브라더선, 프레스턴 피시의 <현명한 투자자>

 

투자 전략에 대한 더 자세한 분석은 아래의 글을 참조하면 된다.

 

 

[주식 공부] 퀀팅으로 벤저민 그레이엄 따라 하기 - 방어 투자자 ver. (1편)

*** 절대 투자 권유나 추천이 아닙니다 *** 퀀팅을 할 수 있는 다양한 지표들이 있지만 개인이 원하는 지표...

blog.naver.com

 

 

2. 방어 투자자를 위한 7가지 투자 전략

 

(1) 기업의 적정 규모:

 

- 연 매출액(revenue)이 3억 5천 달러 이상인 종목

(책에서는 연 매출액 1억 달러 이상을 기준으로 삼았으나, 인플레이션을 고려하여 3억 5천 달러를 기준으로 삼았다. )

 

 

(2) 재무 구조의 건전성:

 

- 유동비율(current ratio)이 2 이상인 종목

- 장기부채가 운전자본 이하인 종목

(장기부채 = 총 부채(total liabilities) - 총 유동부채(total current liabilities),

운전자본 = 총 유동 자산(total current assets) - 총 유동 부채(total current liabilities))

  

 

(3) 이익의 안정성:

 

- 지난 10년 동안 해마다 이익을 낸 종목, 즉 10년간 당기순이익(net income)이 마이너스가 없는 종목

 

 

(4) 배당 실적:

 

- 지난 20년간 빠짐없이 배당을 지급한 종목

 

 

(5) 이익 증가:

 

- 지난 10년간 3년 평균 EPS 증가율이 33% 이상인 종목

(1 ~ 3년 전 3년 평균 EPS가, 8 ~ 10년 전 3년 평균 EPS보다 33% 이상 많은 종목)

 

 

(6) 적정 PER:

 

- 현재 주가가 최근 3년 평균 이익의 15배 이하인 종목

(최근 3년 평균 이익은 주당 순이익인 EPS의 평균으로 구한다.

따라서 "현재 주가 / 3년 평균 EPS <= 15"를 만족하는 종목을 선정한다.)

 

 

(7) 적정 PBR:

 

- 현재 PBR이 1.5 이하인 종목

- PBR * PER이 22.5 이하인 종목

(PER은 (6)에서 구한 PER(= 현재 주가 / 3년 평균 EPS) 값을 사용했다.)

 

 

 

 

첫 번째, 매출액(revenue)이 3억 5천 달러 이상이며, 유동비율(current ratio)이 2 이상인 종목 탐색 (투자 전략 (1), (2) - 1)

 

첫 번째 조건은 investing.com에서 제공하는 stock screener를 통해 만족하는 종목을 쉽게 필터링할 수 있다.

 

 

Stock Screener - Investing.com

Stock Screener - research and filter stocks based on key parameters and metrics such as stock price, market cap, dividend yield and more.

www.investing.com

 

그 후 아래의 코드와 같이 selenium을 이용한 웹 스크래핑으로 필터링 된 종목들의 ticker를 추출해 csv 파일에 dataframe의 형태로 저장했다.

 

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import pandas as pd

driver = webdriver.Chrome('C:/Program Files/chromedriver_win32/chromedriver.exe')
driver.implicitly_wait(3)

tickers = []

for i in range(1, 20):
    driver.get(f'https://www.investing.com/stock-screener/?sp=country::5|sector::a|industry::a|equityType::a|eq_revenue::350000000,27839630000000|qcurratio_us::2,3921.01%3Ceq_market_cap;{i}')
    try:
        driver.find_element_by_link_text('Overview').send_keys(Keys.ENTER)
    except:
        continue
    time.sleep(5)
    html = driver.page_source
    soup = BeautifulSoup(html, 'lxml')
    select = soup.select('.symbol.left.bold.elp')
    for s in select:
        text = s.find_next(class_='left').text
        tickers.append(text)

tickers_df = pd.DataFrame({'Symbol' : tickers})
tickers_df.to_csv('tickers_result_1.csv')

 

 

 

 

두 번째, 장기 부채가 운전자본 이하인 종목 탐색 (투자 전략 (2))

 

먼저 첫 번째 조건을 만족하는 종목들의 총 유동자산, 총 유동부채, 총 부채 정보를 finviz.com에서 웹 스크래핑을 통해 추출하고, 추출한 정보를 dataframe의 형태로 csv 파일에 저장했다.

 

 

FINVIZ.com - Stock Screener

× Ever heard of Finviz*Elite? Our premium service offers you real-time quotes, advanced visualizations, technical studies, and much more. Become Elite and make informed financial decisions. Find out more --> Upgrade your FINVIZ experience Join thousands o

finviz.com

 

코드는 다음과 같다.

 

from bs4 import BeautifulSoup
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time

tickers = pd.read_csv('tickers_result.csv')
tickers = tickers['Symbol'].tolist()

columns = ['Total Current Assets', 'Total Current Liabilities', 'Total Liabilities']
df_filter = pd.DataFrame(index=tickers, columns=columns)

driver = webdriver.Chrome('C:/Program Files/chromedriver_win32/chromedriver.exe')
driver.implicitly_wait(3)

for ticker in tickers:
    driver.get(f'https://finviz.com/quote.ashx?t={ticker}')
    try:
        driver.find_element_by_link_text('balance sheet').send_keys(Keys.ENTER)
    except:
        continue
    time.sleep(5)
    html = driver.page_source
    soup = BeautifulSoup(html, 'lxml')
    for column in columns:
        r = soup.find(text=column).find_next(class_='snapshot-td2').find_next(class_='snapshot-td2').text
        try:
            df_filter.loc[ticker, column] = float(r.replace(',', ''))
        except:
            df_filter.loc[ticker, column] = None

df_filter.to_csv('df_filter_1.csv')

 

다음으로 위에서 가져온 정보들을 바탕으로 각 종목별로 장기부채(=총 부채 - 총 유동부채)와 운전자본(= 총 유동자산 - 총 유동부채)를 계산하고, 장기부채가 운전자본 이하인 종목들을 필터링해 dataframe의 형태로 csv 파일에 저장한다.

 

코드는 다음과 같다.

 

import pandas as pd

df = pd.read_csv('df_filter_result.csv', index_col = 0)
df = df.dropna()
list = []

for i in range(len(df)):
    comp = (df.iloc[i, 0] - df.iloc[i, 1])
    if comp > (df.iloc[i, 2] - df.iloc[i, 1]):
        list.append(df.index[i])

df_result = pd.DataFrame({'Symbol' : list})
df_result.to_csv('tickers_result_2.csv')

 

 

 

 

세 번째, 지난 10년간 당기 순이익(net income)이 마이너스가 없고, 지난 10년간 3년 평균 EPS 증가율이 33% 이상인 종목 탐색 (투자 전략 (3), (5))

 

(두 투자전략에 필요한 정보를 같은 사이트의 같은 페이지에서 스크래핑하므로 같이 분석했다. )

 

먼저 두 번째 조건을 통해 필터링 된 종목들의 10년 간의 당기 순이익과 EPS 정보를 macrotrends.net에서 웹 스크래핑을 통해서 추출하고, 두 가지 정보를 각각 dataframe의 형태로 csv 파일에 저장한다.

 

 

Macrotrends | The Long Term Perspective on Markets

High quality interactive historical charts covering global stock, bond, commodity and real estate markets as well as key economic and demographic indicators.

www.macrotrends.net

 

코드는 다음과 같다.

 

from bs4 import BeautifulSoup
import pandas as pd
from selenium import webdriver

def column(num):
    column = []
    for i in range(1, num+1):
        column.append(f'{i}년 전')
    return column


driver = webdriver.Chrome('C:/Program Files/chromedriver_win32/chromedriver.exe')

df = pd.read_csv('tickers_result_2.csv')
tickers = df['Symbol'].tolist()

df_net_income = pd.DataFrame(index=tickers, columns=column(10))
df_eps = pd.DataFrame(index=tickers, columns=column(10))

for ticker in tickers:
    driver.get(f'https://www.macrotrends.net/stocks/charts/{ticker}/apple/financial-statements')
    driver.implicitly_wait(20)

    #net income 웹 스크래핑
    try:
        button = driver.find_element_by_id('jqxScrollBtnDownhorizontalScrollBarjqxgrid')

        for i in range(40):
            button.click()
    except:
        pass

    html = driver.page_source
    soup = BeautifulSoup(html, 'lxml')
    s1 = soup.select('#row15jqxgrid > div > div')

    for i in range(1, 11):
        try:
            df_net_income.loc[ticker, f'{i}년 전'] = float(s1[i + 1].get_text().replace(',', '')[1:])
        except:
            df_net_income.loc[ticker, f'{i}년 전'] = None

    #eps 웹 스크래핑
    s3 = soup.select('#row21jqxgrid > div > div')

    for i in range(1, 11):
        try:
            df_eps.loc[ticker, f'{i}년 전'] = float(s3[i + 1].get_text()[1:])
        except:
            df_eps.loc[ticker, f'{i}년 전'] = None


df_net_income.to_csv('df_net_income.csv')
df_eps.to_csv('df_eps.csv')

 

다음으로 위에서 가져온 정보들을 바탕으로 10년간 당기 순이익이 마이너스가 없고, 10년간 EPS 성장률이 33% 이상인 종목들을 필터링해 dataframe의 형태로 csv 파일에 저장한다.

 

이때 10년간 EPS 성장률의 경우에는 1~3년 전 3년 평균 EPS와 8~10년 전 평균 EPS를 비교해서 구하게 된다.

 

수식으로 나타내면, ( 1~3년 전 3년 평균 EPS - 8~10년 전 평균 EPS ) / abs( 8~10년 전 평균 EPS ) >= 0.33이다. (abs는 절댓값을 의미한다.) 

 

코드는 다음과 같다.

 

import pandas as pd
import math

def column(num):
    columns = []
    for i in range(1, num+1):
        columns.append(f'{i}년 전')
    return columns

df_net_income = pd.read_csv('df_net_income.csv', index_col=0)
df_eps = pd.read_csv('df_eps.csv', index_col=0)

list = []

#10년간 당기 순이익이 마이너스가 아닌 종목
for i in range(len(df_net_income)):
    for j in range(10):
        if math.isnan(df_net_income.iloc[i, j]) :
            pass
        elif (df_net_income.iloc[i, j]) >= 0:
            pass
        else:
            break
        if j == 4:
            list.append(df_net_income.index[i])
            
list1 = []

#10년간 3년 평균 EPS 성장률이 33% 이상인 종목
for i in list:
    total = 0
    count = 0
    for j in range(1, 4):
       if math.isnan(df_eps.loc[i, f'{j}년 전']):
           pass
       else:
            total = total + df_eps.loc[i, f'{j}년 전']
            count = count + 1
    try:
        recent_eps = total/count
    except:
        recent_eps = float('nan')

    total = 0
    count = 0
    for j in range(8, 11):
       if math.isnan(df_eps.loc[i, f'{j}년 전']):
           pass
       else:
            total = total + df_eps.loc[i, f'{j}년 전']
            count = count + 1
    try:
        past_eps = total/count
    except:
        recent_eps = float('nan')

    if (math.isnan(recent_eps) == False)  & (math.isnan(past_eps) == False):
        if (recent_eps - past_eps) / abs(past_eps) >= 0.33:
            list1.append(i)
    else:
        pass

df_result = pd.DataFrame({'Symbol' : list1})
df_result.to_csv('tickers_result_3.csv')

 

 

 

3. 요약

 

이번 글에서는 방어 투자자를 위한 벤저민 그레이엄의 7가지 투자 전략 중 4가지 투자 전략을 미국 주식에 적용해보았다. 

 

다음 글에서는 나머지 3가지 투자 전략을 미국 주식에 적용해보고 적용한 결과 선정되는 종목에 대해서 알아볼 것이다.