본문 바로가기

주식

CFO, PCR 구하기 - 다모다란 교수 투자방법(2)

by 노이반 2023. 6. 24.
 

다모다란 교수 투자법 - 코드 만들기 전 배경지식 (CFO, PCR)

다모다란 교수님은 투자를 관심 있어하고, 그중에서도 가치투자를 아는 사람이라면 꽤 유명한 교수님이다. 가치투자의 대가로 불리기도 하고, 최근 큰 상승을 했던 엔비디아의 투자를 몇 년 전

nothinkivan.tistory.com

 

CFO와 PCR에 대해서는 여기서 읽어보고 오시면 좋을 것 같고,

 

지난번 미국주식 Ticker(종목코드) 가져오는 것 이후 작업을 시작해 보겠습니다.

 

 

미국주식 ticker 종목코드 불러오기 - 다모다란 교수 투자법 (1)

미국주식에 대해서 먼저 확인을 해볼 예정이기 때문에 미국주식의 모든 종목을 불러와야 한다. 여기저기 찾아보니까 종목을 불러오는 방법은 몇 개가 있는데, 1. 유료 api 활용해서 종목 정보 가

nothinkivan.tistory.com

 

며칠 만에 글을 썼는데, 그 이유는...

 

1. 내가 아직 코딩 초보라서..?

2. 생각보다 작업이 지연돼서... 이유는 아래를 보시면 알 수 있어요.

 

 

--- 보시다가 언제든지 피드백 주시면 환영입니다..

 

 

 

미국주식 ticker 가져오기

import FinanceDataReader as fdr
import pandas as pd


def get_ticker():
    nasdaq = fdr.StockListing('NASDAQ')
    nyse = fdr.StockListing('NYSE')
    amex = fdr.StockListing('AMEX')

    df = pd.concat([nasdaq, nyse, amex])
    df1 = df.copy()
    df1 = df1.drop_duplicates('Symbol')
    
    return df1['Symbol']

tickers_all = get_ticker()

간단하게 설명하면서 넘어가 볼게요.

우선, 저는 여기서 지난번에 얘기한 FinanceDataReader를 활용해서 미국 주식 ticker를 가져오는

get_ticker를 만들었어요.

 

 

tickers_all = get_ticker()로 불러들이기 위한 작업까지.

 

from yahooquery import Ticker
from tqdm import tqdm

# 전체 미국시장 ticker
tickers = tickers_all.to_list()
# tickers = ['AAPL', 'TSLA', 'PLTR','NVDA','SOFI','COIN','AMZN']
tickers = tickers[:100]

symbol = Ticker(tickers)

print(symbol)

# CFO : 영업활동현금흐름 == 영업이익 + 감가상각비 - 법인세 (별도항목으로 기재)
cash = symbol.cash_flow(trailing=False)
cashflow = cash['CashFlowFromContinuingOperatingActivities']

date = cash['asOfDate'].dt.strftime('%Y-%m-%d')

# 최근 CFO 구하기
last_cashflows = [] # 최근 1년 CFO
last_cashflows2 = [] # 최근 2년전 CFO
last_cashflows3 = [] # 최근 3년전 CFO

for ticker in tqdm(tickers):
    if ticker in cashflow and isinstance(cashflow[ticker], pd.Series) and len(cashflow[ticker]) >= 1:
        last_cashflow = cashflow[ticker].iloc[-1]
        last_cashflows.append(last_cashflow)
    else:
        last_cashflows.append(None)

    if ticker in cashflow and isinstance(cashflow[ticker], pd.Series) and len(cashflow[ticker]) >= 2:
        last_cashflow2 = cashflow[ticker].iloc[-2]
        last_cashflows2.append(last_cashflow2)
    else:
        last_cashflows2.append(None)

    if ticker in cashflow and isinstance(cashflow[ticker], pd.Series) and len(cashflow[ticker]) >= 3:
        last_cashflow3 = cashflow[ticker].iloc[-3]
        last_cashflows3.append(last_cashflow3)
    elif ticker in cashflow and isinstance(cashflow[ticker], pd.Series) and len(cashflow[ticker]) >= 2:
        last_cashflows3.append(None)
    else:
        last_cashflows3.append(None)

yahooquery를 활용해서 가져온 ticker를 넣어서 데이터를 가져오는 방식을 선택했어요.

 

tickers = tickers_all.to_list()
# tickers = ['AAPL', 'TSLA', 'PLTR','NVDA','SOFI','COIN','AMZN']
# tickers = tickers[:1000]

 

 

 

이 부분에는 많은 이유가 있습니다.

 

tickers = ticekrs[:100]으로 했을 때는 무난하고 빠르게 돌아갔거든요...? 

약 3~40초 정도? 

 

근데 tickers[:1000]으로 하니까 미친 듯이 처리속도가 느려지더라고요....?

10시간이 넘어가도 안 끝나고.... 원인을 모르겠음..

 

 

이유가 뭘까 해서 코드를 분리해서 쪼개서 실행해 보면서 찾았습니다.

 

# 시가총액
market_caps = []

for ticker in tqdm(tickers):
    sym_detail = symbol.summary_detail[ticker]
    if isinstance(sym_detail, dict):
        market_cap = sym_detail.get('marketCap')
        market_caps.append(market_cap)
    else:
        market_caps.append(None)
        
print("시가총액:", market_caps)

 

여기!! 시가총액 marketcap을 가져오는 코드가 엄청 오래 걸리더라고요.

(ticker 100개는 괜찮음... 본인이 궁금한 ticker만 리스트로 해서 해도 금방 됩니다.)

 

1개 가져오는데 대략 20~30초 정도 소요...

근데 위에서 리스트로 만든 7개 가져올 때는, 3초 걸림.. (??????)

 

dictionary를 ticker별로 들어가서 찾아서 가져와서 오래 걸리는 건지...

제 코드가 문제인 건지 너무 오래 걸려서 우선 빼놓고 돌릴게요. 

 

(도움을 주실 수 있는 분이 있다면 꼭 찾아주세요... 제발)

 

 

PCR 비율 = 시가총액 / 최근 CFO

아래와 같이 코드를 작성했습니다.

# PCR 비율 구하기 
pcr_ratios = []

for market_cap, last_cashflow in tqdm(zip(market_caps, last_cashflows)):
    if market_cap is None or last_cashflow is None:
        pcr_ratio = None
    else:
        pcr_ratio = market_cap / last_cashflow
    pcr_ratios.append(pcr_ratio)
    
print("PCR 비율:", pcr_ratios)

 

그리고 CFO가 연속적으로 증가를 했는지 체크하기 위해서는 다음과 같이!

con_increase = []

for ticker in tickers:
    cfo_current = last_cashflows[tickers.index(ticker)]
    cfo_prev = last_cashflows2[tickers.index(ticker)]
    cfo_prev2 = last_cashflows3[tickers.index(ticker)]

    if (cfo_current is not None and 
        cfo_prev is not None and 
        cfo_prev2 is not None and
        cfo_current > cfo_prev and 
        cfo_prev > cfo_prev2):
        
        con_increase.append('Y')
    else:
        con_increase.append('N')

 

 

최종적으로 출력되는 결과물을 보시면 아실 텐데,

숫자단위가 커서 백만단위로 포맷을 정리하는 걸로 작성했고, 

데이터프레임을 생성해서 깔끔하게 볼 수 있도록 작성했어요.

# 숫자 형식 설정
pd.options.display.float_format = '{:,.0f}'.format

# 값을 백만 단위로 변환하는 함수
def format_millions(value):
    if isinstance(value, list):
        return [f"{v / 1_000_000:,.0f} M" if v else None for v in value]
    else:
        return f"{value / 1_000_000:,.0f} M" if value else None

# 데이터프레임 생성
df = pd.DataFrame({
    'US Stock': tickers, 
    'PCR': pcr_ratios, 
    'Continuous Increase': con_increase
})

# CFO 데이터를 백만 단위로 변환하고 데이터프레임에 추가
for i, cf_data in enumerate(tqdm([last_cashflows, last_cashflows2, last_cashflows3], desc="Formatting Data")):
    df[f'{date[3-i]}'] = [format_millions(cf) for cf in cf_data]

df.head(10)

 

이렇게까지 실행하고 나면 아래와 같은 결과가 나옵니다..

(시가총액의 늪을 해결하지 못해서 며칠 동안 머리 싸맨 시간이 더 길었어요..)

 

무한루프에 빠진 줄 알고 뭐가 문제인지... 헤매는 시간도 있었고

위에서 날짜 옆에 시간이 거슬려서... 이것도 코드 수정 추가

 

 

yahooquery의 구조가 데이터를 가져올 때, 시간이 좀 많이 걸리는 구조인 건지

제가 코드를 효율적으로 못 짜는 건지 몰라서 그게 너무 답답하네요..

 

 

이것 때문에 tqdm도 추가해서 progress bar로 진행되는 상황도 확인하고,

중간중간 print문도 넣어서 진행상태 체크할 수 있게 하고 이런 사소하지만 중요한 것들을 익히게 되었습니다.

 

하지만 우선 하고자 하는 목표까지는 달성해서.. 

여기서 다음 업그레이드는 어떤 걸 해야 할지 고민입니다.

 

단순히 필터링만 해서 보면 될지?

이걸 투자에 반영하기 위해서는 실제 기업의 보고서나 이런 부분들도 반영해야 되나...?라는 여러 가지 생각이 드는데

이건 금융공부를 좀 해야겠죠...?

 

그래서 당장은 이 코드를 속도개선이 된다면

텔레그램 봇과 연결해서 사용할 수 있는 방법을 생각해보려고 합니다.

 

관심 있으시거나 아이디어가 있으신 분들은 편하게 제안해 주시면 감사하겠습니다.

 

 

댓글