프로그래밍/파이썬

[Python] 밸류에이션 지표 계산 봇 만들기 (yahoo_fin 이용)

퀀트매니아 2023. 2. 2. 22:54
728x90

Python - 밸류에이션 지표 계산 봇 만들기 (yahoo_fin 이용)

 

안녕하세요.

오늘은 파이썬과 텔레그램을 이용하여 주식의 밸류에이션 지표를 계산하는 봇을 만들어보는 시간을 가져보도록 하겠습니다.

 

밸류에이션 계산 봇을 만들게 된 계기는, 야후 파이낸스에서 확인할 수 있는 밸류에이션 지표가 주식 시장이 마감된 후에 반영이 된다는 점, FED 모델을 적용하기 위해 PER Yield 값을 계산기로 계산해야 하는 수고를 덜고 싶었고, 또 PEG를 실시간 가격으로 계산해보고 싶은 점 등의 이유로 밸류에이션 지표 계산 봇을 만들게 되었습니다.

 

 

Yahoo Finance의 데이터와 Webull의 데이터 비교

 

 

 

필요 라이브러리

 

먼저, yahoo_fin과 python-telegram-bot 라이브러리가 필요합니다. 아래 명령어를 이용하여 설치를 진행하시면 되겠습니다.

 

pip install yahoo_fin
pip install python-telegram-bot

 

 

 

전체코드

 

다음은 전체 코드입니다. 채팅방에서 '/sts' 명령어와 함께 주식 티커를 입력하면 밸류에이션 지표를 계산하는 코드입니다.

 

import yahoo_fin.stock_info as si
import telegram
from telegram import Update
from telegram.ext import CommandHandler, ApplicationBuilder, ContextTypes

# 텔레그램 정보
s_token = '텔레그램 토큰 입력'

async def bot_send_msg(p_text, p_chat_id):
    """ 텔레그램 메세지 전송 함수 """
    bot = telegram.Bot(s_token)
    async with bot:
        await bot.send_message(text=p_text, chat_id=p_chat_id)

async def sts_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """ sts 커맨드 로직 """

    # 0인 제수 방지함수
    def weird_division(n, d):
        return n / d if d else 0

    # chat id 가져오기
    chat_id = update.message.chat.id

    # 값 입력이 없는 경우 안내 메세지
    if len(context.args) < 1:
        await bot_send_msg("값이 충분히 입력되지 않았습니다.\n확인하여주십시오.", chat_id)
        return
    
    await bot_send_msg("로딩중...", chat_id)

    # 대문자로 바꾸기
    p_ticker = context.args[0].upper()

    # 주식 정보 불러오기
    info_data = si.get_quote_data(p_ticker)

    # 주식수 불러오기
    if info_data['sharesOutstanding'] is None:
        shares = 0
    else:
        shares = round(info_data['sharesOutstanding'])

    # 현재 주가 불러오기
    if info_data['regularMarketPrice'] is None:
        current_price = 0
    else:
        current_price = round(info_data['regularMarketPrice'], 2)

    # 시가총액 계산하기
    market_cap = shares * current_price
    r_market_cap = market_cap # 계산용 시가총액

    # 시가총액 문자로 바꾸기
    if abs(market_cap) >= 1000000000000:
        market_cap = round(market_cap / 1000000000000, 2)
        s_market_cap = str(market_cap) + 'T'
    elif abs(market_cap) >= 1000000000:
        market_cap = round(market_cap / 1000000000, 2)
        s_market_cap = str(market_cap) + 'B'
    elif abs(market_cap) >= 1000000:
        market_cap = round(market_cap / 1000000, 2)
        s_market_cap = str(market_cap) + 'M'
    elif abs(market_cap) >= 1000:
        market_cap = round(market_cap / 1000, 2)
        s_market_cap = str(market_cap) + 'K'
    else:
        s_market_cap = str(round(market_cap))

    # 전일 종가 불러오기
    if info_data['regularMarketPreviousClose'] is None:
        pre_price = 0
    else:
        pre_price = round(info_data['regularMarketPreviousClose'], 2)

    # 성장률 가져오기
    analysts_data = si.get_analysts_info(p_ticker)
    
    if analysts_data['Growth Estimates'].iloc[4,1] is None:
        eps_growth = '0%'
    else:
        eps_growth = analysts_data['Growth Estimates'].iloc[4,1]

    # OCF 불러오기
    sts_data = si.get_stats(p_ticker)
    s_ocf_ttm = sts_data.iloc[49, 1]
    
    ocf_ttm = 0
    if str(s_ocf_ttm) is None:
        ocf_ttm = 0
    elif "T" in str(s_ocf_ttm):
        ocf_ttm = float(str(s_ocf_ttm).split("T")[0]) * 1000000000000
    elif "B" in str(s_ocf_ttm):
        ocf_ttm = float(str(s_ocf_ttm).split("B")[0]) * 1000000000
    elif "M" in str(s_ocf_ttm):
        ocf_ttm = float(str(s_ocf_ttm).split("M")[0]) * 1000000

    print(ocf_ttm)

    # 순이익 가져오기(TTM)
    s_net_income = sts_data.iloc[40, 1]

    # 순이익 숫자로 변환
    ttm_net_income = 0
    if str(s_net_income) is None:
        ttm_net_income = 0
    elif "T" in str(s_net_income):
        ttm_net_income = float(str(s_net_income).split("T")[0]) * 1000000000000
    elif "B" in str(s_net_income):
        ttm_net_income = float(str(s_net_income).split("B")[0]) * 1000000000
    elif "M" in str(s_net_income):
        ttm_net_income = float(str(s_net_income).split("M")[0]) * 1000000

    # PER 계산하기(TTM)
    per_ttm = round(weird_division(r_market_cap, ttm_net_income), 2)

    # 성장률 가져오기
    analysts_data = si.get_analysts_info(p_ticker)
    
    if analysts_data['Growth Estimates'].iloc[4,1] is None:
        eps_growth = '0%'
    else:
        eps_growth = analysts_data['Growth Estimates'].iloc[4,1]

    r_eps_growth = float(eps_growth[0:len(eps_growth)-1])

    # PEG 계산하기
    peg = round(weird_division(per_ttm, r_eps_growth), 2)
        
    # 10년물 국채금리 불러오기
    bonds_info_data = si.get_quote_data('^TNX')
    
    if bonds_info_data['regularMarketPrice'] is None:
        bonds_current_price = 0
    else:
        bonds_current_price = bonds_info_data['regularMarketPrice']

    # 최종 메세지 작성
    total_msg = "[ " + info_data["displayName"] + " 밸류에이션 지표 계산 ]\n"

    total_msg = total_msg + "\n" + " 1. 시가총액 : " + s_market_cap + "\n"
    total_msg = total_msg + " 2. 현재가 : " + str(current_price) + " (" + str(round((current_price - pre_price) / pre_price * 100, 2)) + "%) \n"
    total_msg = total_msg + " 3. P/E(TTM) : " + str(per_ttm) + "\n"
    total_msg = total_msg + " 4. PEG : " + str(peg) + "\n"
    total_msg = total_msg + " 5. 성장률(5y) : " + eps_growth + "\n"
    total_msg = total_msg + " 6. POCF : " + str(round(weird_division(r_market_cap, ocf_ttm), 2)) + "\n"
    total_msg = total_msg + " 7. 순이익(TTM) : " + str(s_net_income) + "\n"

    if per_ttm == 0:
        total_msg = total_msg + " 8. Earnings Yield : 판단불가\n"
    else:
        total_msg = total_msg + " 8. Earnings Yield : " + str(round(weird_division(1, per_ttm) * 100, 2)) + "%\n"

    total_msg = total_msg + "※ 미국 10년물 국채금리 : " + str(round(bonds_current_price, 4)) + "%"

    await bot_send_msg(total_msg, chat_id)


if __name__ == '__main__':

    # 챗봇관련 로직
    application = ApplicationBuilder().token(s_token).build()

    sts_handler = CommandHandler('sts', sts_command)
    application.add_handler(sts_handler)
    
    application.run_polling()

 

먼저, 위 코드 실행결과를 살펴보겠습니다.

 

 

명령어를 이용한 밸류에이션 지표 계산 결과

 

 

코드를 실행한 뒤에 '/sts'라는 명령어와 함께 주식의 티커를 입력하면 지표들을 산출하는 코드가 실행됩니다. 지표가 모두 산출이 되면, 지표들을 이용하여 최종 메시지를 작성하고 채팅방으로 메시지를 전송하게 됩니다. 채팅방에 보내진 메시지를 확인해 보니 시가총액과 PER 등의 지표들이 현재가격을 이용하여 잘 산출되었다는 것을 볼 수 있었습니다.

 

 

 

코드 설명

 

다음으로, 코드에 대해 간략히 설명을 드리도록 하겠습니다.

 

async def bot_send_msg(p_text, p_chat_id):
    """ 텔레그램 메세지 전송 함수 """
    bot = telegram.Bot(s_token)
    async with bot:
        await bot.send_message(text=p_text, chat_id=p_chat_id)

 

▲ 텔레그램 라이브러리가 v20.0으로 업데이트되면서 비동기 방식으로 변경되었기 때문에, 메시지를 전송할 때 async 키워드와 awiat 키워드를 이용하여 봇의 함수를 실행하도록 코드를 구성했습니다.

 

 

    # 0인 제수 방지함수
    def weird_division(n, d):
        return n / d if d else 0

 

▲분모가 0일 경우의 오류가 나는 부분을 해결하기 위해 나누기 함수를 만들었습니다. 

 

 

    # 시가총액 문자로 바꾸기
    if abs(market_cap) >= 1000000000000:
        market_cap = round(market_cap / 1000000000000, 2)
        s_market_cap = str(market_cap) + 'T'
    elif abs(market_cap) >= 1000000000:
        market_cap = round(market_cap / 1000000000, 2)
        s_market_cap = str(market_cap) + 'B'
    elif abs(market_cap) >= 1000000:
        market_cap = round(market_cap / 1000000, 2)
        s_market_cap = str(market_cap) + 'M'
    elif abs(market_cap) >= 1000:
        market_cap = round(market_cap / 1000, 2)
        s_market_cap = str(market_cap) + 'K'
    else:
        s_market_cap = str(round(market_cap))

 

▲ 시가 총액의 경우 보기가 좋게 단위를 넣어주었습니다.

 

 

    # 순이익 숫자로 변환
    ttm_net_income = 0
    if str(s_net_income) is None:
        ttm_net_income = 0
    elif "T" in str(s_net_income):
        ttm_net_income = float(str(s_net_income).split("T")[0]) * 1000000000000
    elif "B" in str(s_net_income):
        ttm_net_income = float(str(s_net_income).split("B")[0]) * 1000000000
    elif "M" in str(s_net_income):
        ttm_net_income = float(str(s_net_income).split("M")[0]) * 1000000

 

▲ 반대로, 숫자 계산이 필요한 단위가 들어가 있는 값은 단위만큼 숫자를 곱해주었습니다.

 

 

 

마무리...

 

오늘은 밸류에이션 지표를 계산해 주는 봇을 파이썬과 텔레그램을 이용하여 만들어 보았습니다.

 

위 코드를 이용하여 제가 산출해 본 지표 말고도 추가적으로 확인하고 싶으신 지표들은 yahoo_fin을 이용하셔서 추가적인 데이터를 구하신 후 지표를 산출해 사용하시면 될 것 같습니다.

 

다음시간에도 유용한 정보를 소개하는 시간으로 찾아뵙도록 하겠습니다. 감사합니다!

 

728x90