수익률 제대로 구하기 1

랜디 Randy·2023년 12월 30일
0

Preview

어디서부터 잘못됐는지 알아기 위해 작성한 수많은 print문들......


문제 인식

지금와서 생각해보니 pykrx에서도 고쳐야할 부분이 있는데, 우선 yfinance에서 수정한 부분만 자세히 풀어보도록 하겠습니다.

yfinance의 경우

# Nasdaq
NASDAQ = '^IXIC'  # Ticker symbol for Nasdaq in yfinance
nasdaq_data = yf.download(NASDAQ, monday_obj, saturday_obj)

형식으로 데이터를 제공하면 월요일부터 금요일까지의 데이터를 반환합니다.
끝 날짜는 포함하지 않더군요.


문제는 수익률을 '제대로' 구하려고 하다가 발생했습니다.

# Nasdaq
nasdaq_data = yf.download(NASDAQ, start=formatted_monday, end=formatted_friday)
nasdaq_result = (nasdaq_data['Adj Close'].pct_change().fillna(0) + 1).prod() - 1
# Adj Close: Adjusted Closing Prie; 조정 종가
# pct_change(): get daily fluctuation rate of 'Adj Close'
# fillna(0): replace NaN to 0
# + 1: to calculate compound interest
# prod(): multiple all daily fluctuation rate
# - 1: to get Nasdaq's weekly pure fluctuation rate
total_weekly.at[idx, 'NASDAQ'] = nasdaq_result

이게 chatGPT가 작성해준 주간 나스닥 수익률을 구하는 코드이고, 주석은 제가 함수를 까먹을까봐 각각의 함수의 역할을 적어놓은겁니다.

여기서 하나하나 생각해보다가 '굳이 이렇게 해야해..?' 하는 생각이 들더군요. 특히 prod() 부분과 pct_change() 굳이 필요한가.. 싶었습니다. yfinance가 리턴하는 데이터를 직접 살펴보니 그렇더군요. 그래서 chatGPT한테 반문했더니 헛소리를 시작하길래

내가 해야겠군... 이라고 생각했습니다.


기존 코드의 문제점

월요일에서 금요일까지의 데이터를 받았을 때,

  • 기존 코드
    월요일 종가 -> 화요일 종가로의 등락률 계산,
    화요일 종가 -> 수요일 종가로의 등락률 계산, .....
    금요일 종가까지 끝낸 후 이를 종합하여 주간 수익률을 구합니다.

  • 문제점1
    기본적으로 수익률은
    ((매도가격 / 매수가격) - 1) * 100
    으로 구합니다.
    따라서 기존 코드에서 사용하는 방법은 불필요한 작업이 너무 많이 이루어집니다.
    다시 말해, ((금요일 종가 / 월요일 시가) - 1) * 100 계산 한 번이면 주간 수익률을 구할 수 있는데
    요일이 바뀔 때마다 계산을 하고있으니
    불필요한 연산이 많이 발생하고 있는 것이죠

  • 문제점2
    기존 코드는 첫 시작이 월요일 종가입니다. 즉, 월요일의 가격 변동성은 고려하지 않는다는 뜻입니다. 월요일 시가로 계산해야 정확한 수익률을 구할 수 있습니다.
    월요일 하루의 변동성만 무시하는 것이기도 하고, 지수 자체의 움직임이 그렇게 크지 않기때문에 제대로 구한 수치와 큰 차이는 없습니다.
    다만 주식시장에는 복리라는 것이 적용되기 때문에 작은 오차도 눈덩이처럼 불어서 뒤늦게 큰 차이를 불러일으킬 수 있습니다.

이렇게 두 가지 문제점을 해결하는 것이 이번 과제였습니다.


해결과정

이전 날짜로 바꾸기

그래서 월요일의 경우 시가('Open')을 참조해서 코드를 변경하려고 했는데, 에러가 발생했습니다.

yfinance의 경우 holiday로 인해 평일에 개장하지 않을 경우, 그 날의 데이터는 빈 데이터를 반환하는게 아니라 그 날 자체를 반환하지 않습니다.
그러니까 월화수목금 중에서 월요일이 휴장일이었다면, 월요일의 데이터는 빈 데이터 혹은 0으로 채워진 데이터를 반환하는 것이 아니라 화수목금의 데이터만 반환하는 것입니다.

그래서 처음 생각한 방법이

그럼 try-except 문을 활용해서, 월요일 데이터를 참조했을 때 에러가 발생하면 이전 영업일의 날짜로 변경해서 그 날의 종가를 참조하는 방법이었습니다.

화수목금이면 괜찮지만, 월요일의 경우 이전 영업일은 저번주 금요일이기 때문에 월요일과 화수목금의 처리방식이 달라야합니다. while True문을 활용하여 현재 참조하고자 하는 날짜가 없다면, 날짜를 하루 앞당기는 반복문을 설정해서 이를 해결하고자 했습니다.

다만 제가 코딩을 잘 못해서인지 루프가 돌지 않거나, 데이터가 있음에도 무한 루프를 돌게되는 사태가 자꾸 발생했습니다.

아무래도 날짜 데이터를 string 타입과 datetime 타입 사이를 자꾸 왔다갔다 해야하다보니 그 과정에서 문제가 생긴 것 같습니다.

거진 4일을 이걸 해결하기 위해 매달렸습니다... 왜 날짜가 안바뀔까,.. 왜 바뀌는데 안멈추는걸까... 이러면서 말이죠....

인덱스를 참조하여 해결

결국 이면지를 꺼내 제가 짠 코드의 알고리즘을 하나하나 되짚어보며 처음부터 다시 시작해봤습니다.

그러다가 AHA-MOMENT! 가 찾아오면서 인덱스로 해결하면 되겠다! 라는 생각을 하게 됐는데

월화수목금에서 휴장일로 인해 화수목금의 데이터만 반환된다면, 그것도 어쨌든 일주일입니다. 즉, 월요일의 시가가 없다고 해서 저번주 금요일의 종가를 데리고 오는게 아니라, 그냥 개장한 첫 날의 시가를 참조하면 되는 일이었습니다.

아무래도 '일주일은 무조건 5일이어야 해...!!!' 하는 사고방식에 갇혀있다 보니 인덱스로 해결하자는 생각을 그 당시에는 못했던 것 같습니다.


개선사항

def get_weekly_nasdaq_fluctuation():
    NASDAQ = '^IXIC'  # Ticker symbol for Nasdaq in yfinance

    for idx in weekly_dates_df.index:

        monday = weekly_dates_df.loc[idx, 'Monday']
        friday = weekly_dates_df.loc[idx, 'Friday']

        # transform string to datetime object
        monday_obj = datetime.strptime(monday, '%Y%m%d')
        saturday_obj = datetime.strptime(friday, '%Y%m%d') + timedelta(days=1)

        # Nasdaq
        nasdaq_data = yf.download(NASDAQ, monday_obj, saturday_obj)

        tmp_nasdaq = nasdaq_data.copy()
        tmp_price = tmp_nasdaq.iloc[0,0]
        tmp_nasdaq.iloc[0,4] = tmp_price

        nasdaq_result = (((tmp_nasdaq['Adj Close'].iloc[-1] / tmp_nasdaq.iloc[0,0]) - 1) * 100)
        # nasdaq_result = ((tmp_nasdaq['Adj Close'].pct_change().fillna(0) + 1).prod() - 1) * 100
        
        total_weekly.at[idx, 'NASDAQ'] = nasdaq_result


    return total_weekly

해당 코드입니다. 맨 마지막에 nasdaq_result 변수를 할당하는 방법이 두 가지가 있는데, 둘 중 어느 방법을 사용하더라도 결과는 동일하게 나옵니다.

첫 번째 방법이 직관적으로 어떤 것을 어떻게 연산하는건지 알기가 쉽습니다.
주석 처리해놓은 두 번째 방법은 예외처리를 더 염두에 둔 것 같은데,
아마 일주일이 전부 휴장일이거나 할 경우 반환되는 데이터가 없기때문에 이를 유념해야 할 것 같습니다.

반환되는 데이터가 없는 경우 그냥 데이터를 0으로 처리하는 경우도 생각해봐야겠습니다. 일주일 전체가 쉬었을 경우 주간 수익률은 0%이기 때문이죠.

혹은 기본적으로 데이터프레임에 0을 채워뒀다가, 반환되는 데이터가 없는 경우 그냥 skip하는 방법도 있습니다만, 에러가 발생해서 skip된 것인지 일주일 전체가 휴장일이라 0이 입력된 것인지 알 길이 없기때문에 이 방법은 조금 더 고민해볼 필요가 있습니다.


전체 코드

!pip install pykrx
!pip install yfinance

from pykrx import stock
import yfinance as yf

from datetime import datetime, timedelta
import pandas as pd

def get_weekly_benchmark_fluctuation_rate(start_date, end_date):

  # Make DataFrame which have weeekly date
  def get_every_monday_friday(start_date, end_date):
      # Translate input dates into datetime objects
      start_date_dt = datetime.strptime(start_date, "%Y%m%d")
      end_date_dt = datetime.strptime(end_date, "%Y%m%d")

      # Initialize an empty list to store weekly dates
      weekly_dates = []

      current_date = start_date_dt

      # Iterate through weeks until the end_date
      while current_date <= end_date_dt:
          # Calculate the Monday of the current week
          week_start = current_date - timedelta(days=current_date.weekday())

          # Calculate the Friday of the current week
          week_end = week_start + timedelta(days=4)

          # Append the start and end dates of the current week to the list
          weekly_dates.append((week_start.strftime("%Y%m%d"), week_end.strftime("%Y%m%d")))

          # Move to the next week
          current_date = week_end + timedelta(days=3)  # Move to the next Monday

      # Create a DataFrame with weekly Mondays and Fridays
      df = pd.DataFrame(weekly_dates, columns=['Monday', 'Friday'])
      return df

  # Append Data of benchmark's fluctuation rate into DataFrame
  def get_weekly_fluctuation(weekly_dates):
    KOSPI = 'KOSPI'
    KOSDAQ = 'KOSDAQ'
    NASDAQ = '^IXIC'  # Ticker symbol for Nasdaq in yfinance

    total_weekly = weekly_dates.copy()
    total_weekly['KOSPI'] = 100
    total_weekly['KOSDAQ'] = 100
    total_weekly['NASDAQ'] = 100

    for idx in weekly_dates.index:
        # Get index's monday and friday date
        monday = weekly_dates.loc[idx, 'Monday']
        friday = weekly_dates.loc[idx, 'Friday']

        # KOSPI and KOSDAQ's weekly price change rate
        # KOSPI
        weekly_kospi_full = stock.get_index_price_change(monday, friday, KOSPI)
        kospi_result = weekly_kospi_full.iloc[0,2]
        total_weekly.at[idx, 'KOSPI'] = kospi_result

        # KOSDAQ
        weekly_kosdaq_full = stock.get_index_price_change(monday, friday, KOSDAQ)
        kosdaq_result = weekly_kosdaq_full.iloc[0,2]
        total_weekly.at[idx, 'KOSDAQ'] = kosdaq_result

        # Get nasdaq's weekly price change rate
        # transform string to datetime object
        monday_obj = datetime.strptime(monday, '%Y%m%d')
        saturday_obj = datetime.strptime(friday, '%Y%m%d') + timedelta(days=1)

        # Nasdaq
        nasdaq_data = yf.download(NASDAQ, monday_obj, saturday_obj)

        tmp_nasdaq = nasdaq_data.copy()
        tmp_price = tmp_nasdaq.iloc[0,0]
        tmp_nasdaq.iloc[0,4] = tmp_price

        nasdaq_result = (((tmp_nasdaq['Adj Close'].iloc[-1] / tmp_nasdaq.iloc[0,0]) - 1) * 100)
        # nasdaq_result = ((tmp_nasdaq['Adj Close'].pct_change().fillna(0) + 1).prod() - 1) * 100
        
        total_weekly.at[idx, 'NASDAQ'] = nasdaq_result

    return total_weekly

  weekdays = get_every_monday_friday(start_date, end_date)
  result = get_weekly_fluctuation(weekdays)

  return result

start_date = '20230912'
end_date = '20231030'

total = get_weekly_benchmark_fluctuation_rate(start_date, end_date)
total

함수를 사용하는 예시 코드와, 전체 함수 코드를 첨부합니다.

쓰고싶으신 분들은 쓰시길 바랍니다. 다행히도 아직까지는 잘 작동합니다.

다음에는 pykrx의 수익률 구하는 부분을 고쳐서 오겠습니다. 파이팅!

profile
데이터는 계단, 직관은 다리

0개의 댓글