주식 코딩

[Python] Step#5 백테스트 해보기 1/2

곰발이 2022. 9. 3. 23:51

본격적인 백테스트 코드를 작성해 보도록 하겠습니다.

 

먼저 필요한 라이브러리를 Import 해줍니다.

 

import pandas_datareader as pdr
import pandas as pd
import numpy as np
import math

from datetime import datetime, timedelta
from Get_Price_Data import *

 

Get_Price_Data 파일은 웹페이지에서 주가 정보를 크롤링 하기위한 파일 입니다.

Get_Price_Data 파일은 아래글을 참조하시기 바랍니다.

 

[Python] Step#4 주가 데이터 크롤링 (tistory.com)

 

[Python] Step#4 주가 데이터 크롤링

파이썬을 하는 이유중 하나는 웹페이지의 html 정보를 크롤링하기 쉽다는데 있습니다. 특히 주식 크롤링을 위한 라이브러리는 상당히 다양한데요. 오늘은 그중 제가쓰는 두가지 라이브 러리를

ggombal.tistory.com

 

우선 함수를 선언해 보겠습니다.

 

def Calc_Asset_Balance(Universe,Initial_Balance,start_day,end_day):

 

입력 인자로는 

Universe : 종목들의 티커가 담긴 리스트 입니다.

Initial_Balance : 최초 자산값 입니다.

start_day, end_day : 백테스트의 시작일 과 종료일 입니다.

 

    df_ALL = get_datareader_price_data(Universe, start_day, end_day)
    df_ALL.index = df_ALL.index.values.astype('<M8[m]')

 

해당 코드는 주가 데이터를 df_ALL 이라는 데이터 프레임에 저장 합니다.

그리고 인덱스 ('Date') 의 자리수 중 "시 분 초" 는 의미 없으므로 절삭합니다.

(이해하기 어려워도 상관 없습니다. 그냥 Ctrl C+V 하시고 궁금하면 나중에 찾아보셔도 됩니다^^)

 

# Calculate Profit & log Profit
    for i in range(len(Universe)):
        df_ALL.rename(columns = {Universe[i]:'ASSET'+'{}'.format(i)}, inplace = True)

 

해당 코드는 개인의 개별 티커가 명확하지 않고 몇년주기의 데이터를 전부 병합하기위해 각 자산의 이름을 ASSET#0~15까지 명명하는 작업입니다.

 

    col_list_P = [col+'_P' for col in df_ALL.columns]
    df_ALL[col_list_P] = df_ALL.pct_change()
    df_ALL[col_list_P] = df_ALL[col_list_P]*100

각 자산군의 '_P' 는 Profit 을 뜻합니다.

pct_change() 는 수익률을 구해주는 메서드 이고,

각각의 수익률을 100분율로 변환합니다.

 

# Duration of backtest
    df_ALL = df_ALL[start_day:end_day]

백테스트 날짜를 지정해 줍니다. Get_datareader_price_data 함수는 시작일로부터 두달전 데이터 까지 가져오고 있습니다. 우리가 원하는 백테스트 기간으로 절삭해 줍니다.

 

    for i in range(len(df_ALL)):

        date = df_ALL.index[i]
        temp = 0
        total_Bal = 0
        
# Calculate Balance
        for j in range(Universe_Cnt):
            if i==0:
                temp = ( (Initial_Balance/Universe_Cnt) * (1 + df_ALL['ASSET'+'{}'.format(j)+'_P'].iloc[i]/100))
                df_ALL.loc[date,'ASSET'+'{}'.format(j)+'_BAL'] = temp
            else:
                temp = (df_ALL['ASSET'+'{}'.format(j)+'_BAL'].iloc[i-1] * (1 + df_ALL['ASSET'+'{}'.format(j)+'_P'].iloc[i]/100))
                df_ALL.loc[date,'ASSET'+'{}'.format(j)+'_BAL'] = temp
            
            total_Bal = total_Bal + (df_ALL['ASSET'+'{}'.format(j)+'_BAL'].iloc[i])
                
        df_ALL.loc[date,'ASSET_BAL'] = total_Bal
        
   return df_ALL

이제부터 포트폴리오의 전체 자산을 구해 보도록 하겠습니다.

최초 시작 시 (i==0)는  최초 자산(Initial_Balance) 을 균등 분배한후 수익률을 곱합니다.

이후 부터는 각 자산의 수익률을 따로 계산하고 전체를 합쳐 포트폴리오 자산을 계산합니다.

그리고 전체 데이터프레임 (df_ALL)을 반환 합니다.

 

전체 코드는 아래와 같습니다.

 


def Calc_Asset_Balance(Universe,start_day,end_day):

    Universe_Cnt = len(Universe)
        
    df_ALL = get_datareader_price_data(Universe, start_day, end_day)

    df_ALL.index = df_ALL.index.values.astype('<M8[m]')
    
# Calculate Profit & log Profit
    for i in range(len(Universe)):
        df_ALL.rename(columns = {Universe[i]:'ASSET'+'{}'.format(i)}, inplace = True)

    col_list_P = [col+'_P' for col in df_ALL.columns]
    df_ALL[col_list_P] = df_ALL.pct_change()
    df_ALL[col_list_P] = df_ALL[col_list_P]*100

# Duration of backtest
    df_ALL = df_ALL[start_day:end_day]

# Set Initial Value
    df_ALL['ASSET_BAL'] = Initial_Balance
    
    for j in range(Universe_Cnt):
        df_ALL['ASSET'+'{}'.format(j)+'_BAL'] = 0

# Calculate Balance
    for i in range(len(df_ALL)):

        date = df_ALL.index[i]
        temp = 0
        total_Bal = 0

        for j in range(Universe_Cnt):
            if i==0:
                temp = ( (Initial_Balance/Universe_Cnt) * (1 + df_ALL['ASSET'+'{}'.format(j)+'_P'].iloc[i]/100))
                df_ALL.loc[date,'ASSET'+'{}'.format(j)+'_BAL'] = temp
            else:
                temp = (df_ALL['ASSET'+'{}'.format(j)+'_BAL'].iloc[i-1] * (1 + df_ALL['ASSET'+'{}'.format(j)+'_P'].iloc[i]/100))
                df_ALL.loc[date,'ASSET'+'{}'.format(j)+'_BAL'] = temp
            
            total_Bal = total_Bal + (df_ALL['ASSET'+'{}'.format(j)+'_BAL'].iloc[i])
    
    	df_ALL.loc[date,'ASSET_BAL'] = total_Bal 
        
    return df_ALL

test.py

 

이제 작성한 함수를 호출해 보도록 하겠습니다.

 

import quantstats as qs
import time

from Test import *

## 백테스트 기간 설정 ##
start_day = datetime(2020,1,1)
end_day = datetime.now()

## Universe (포르폴리오) 설정 ##
Universe = ['AAPL','GOOG','FB']

## Main 함수 호출 ##
df_ALL = Calc_Asset_Balance(Universe,start_day,end_day)
print(df_ALL.tail())

main.py

 

df_ALL.tail() 의 의미는 아래서 부터 5줄의 데이터를 의미합니다.

결과물을 보시겠습니다.

 

정상적으로 'ASSET_BAL' 이 계산되고 각 자산의 자산값도 계산되고 있습니다.

 

이제 quantsts 이라는 라이브 러리를 사용하여 포트폴리오를 분석해 보겠습니다.

 

import quantstats as qs

라이브러리를 import 하고

 

qs.reports.html(df_ALL['ASSET_BAL'], mode='full', title='Test',output='E:\Test.html', download_filename='Test.html')

 

리포트를 생성합니다.

 

정상적으로 포트폴리오 리포트가 생성 되었습니다.