-
[Python] 퀀트 투자 기법 적용하기 Part 2. (미국편)Programming 2022. 9. 3. 11:15
지난 번 포스팅에서는 퀀트 투자 기법을 적용하기 위해 필요한 데이터를 수집하는 함수를 만들었다.
그럼 이번 포스팅에서는 실제로 투자 기법을 하나씩 구현해보도록 하자. (데이터는 이미 수집되어 있다는 가정 하)
방법론 조건 판단 기준 Graham - ROA 5% 이상
- Debt ratio 50% 이하
- PBR 0.2 이상PBR 가 낮은 종목부터 매수 NCAV - PER 0.2 이상 5 이하
- (현 자산 - 총 부채) > 시가총액
OR (현 자산 - 총 부채) * 0.85 > 시가총액
- Net income > 0순유동자산(2번째 항목)이 높은 종목부터 매수 3P Combo - PER / PBR / PSR rank (낮은 순) 3P (PER / PBR / PSR) rank 가 낮은 종목부터 매수 PBR + GP/A - PBR rank (낮은 순)
- GP/A rank (높은 순)두 조건의 종합 rank 가 낮은 종목부터 매수 필요한 데이터를 수집하는 내용은 아래 포스팅을 참고하면 된다.
참고) https://thisiswhoiam.tistory.com/8
[Python] 퀀트 투자 기법 적용하기 Part 1. (미국편)
지난 포스팅에서 yahoo finance 에서 미국 주식 데이터를 긁어오는 내용을 다뤘었다. 이번 포스팅에서는 퀀트 기법에 적용해보기 위해, 어떤 데이터들을 먼저 수집해야하는지를 다뤄보자. 우선 적
thisiswhoiam.tistory.com
지난 번에 포스팅했던 내용을 바탕으로 수집된 데이터의 형태는 아래와 같다.
자세히 보면, "MarketCap", "ROA" 열의 데이터는 추가적인 전처리가 필요한 것을 알 수 있다.
- MarketCap: Unit 을 숫자로 변경해주는 작업 필요
- ROA: 특수 문자 "%" 를 제거해주는 작업 필요방법론을 적용하기 전에 위의 두 가지 사항을 먼저 처리해보자.
나는 Market Cap 의 unit 을 아래와 같이 dictionary 형태로 정리해두고, unit 에 맞는 숫자를 곱해주는 방식으로 처리했다.
만일 market_cap = '2.53T' 라고 가정하면, len(market_cap) 값은 문자열의 길이를 나타내므로 5 가 된다.
그렇다면 market_cap[:len(market_cap) - 1] = market_cap[:4] 가 되므로, '2.53' 을 나타낸다.
거기에 unit_dict[market_cap[-1]] 는 unit_dict['T'] 이므로 1000000000000 값을 나타내고, 두 값이 곱해져서 market_cap = 2530000000000 이 된다.
(float 으로 형(type) 변환을 진행하면, 2530000000000.0 을 반환한다.)
이 내용에 lambda 를 이용하면, dataframe 의 열에 전체적으로 적용할 수 있다.
unit_dict = {'T': 1000000000000, 'B': 1000000000, 'G':1000000000, 'M': 1000000, 'k':1000} # data = 위의 dataframe # 방법 1)lambda 함수 내에서 다 처리하는 방법 data['MarketCap'] = data['MarketCap'].apply(lambda x: float(x[:len(x) - 1]) * unit_dict[x[-1]]) # 방법 2) 함수를 정의해서 사용하는 방법 # 좀 더 세밀하게 조건별로 처리할 수 있음 def transformUnit(x): if isinstance(x, str) == True: # 데이터가 string 이 아닌 경우도 있어 if/else 문으로 따로 처리 return float(x[:len(x)-1]) * unit_dict[x[-1]] else: return 0 data['MarketCap'] = data['MarketCap'].apply(lambda x: transformUnit(x))
두 번째로 ROA 값에서 특수문자 "%" 을 제거해보자.
# 방법 1) lambda 함수를 사용하는 방법 data['ROA'] = data['ROA'].apply(lambda x: float(x[:-1])) # 방법 2) 이건 뭐라고 해야하지 # data['ROA'].str 으로 각 element 값을 가공할 수 있음 data['ROA'] = data['ROA'].str[:-1].astype(float)
그리고 그 외에 "PER", "PBR", "PSR", "DebtRatio" 는 string 에서 float 형으로 변환이 필요하다.
for col in ['PER', 'PBR', 'PSR', 'DebtRatio']: data[col] = data[col].astype(float) """ # 동일 data['PER'] = data['PER'].astype(float) data['PBR'] = data['PBR'].astype(float) data['PSR'] = data['PSR'].astype(float) data['DebtRatio'] = data['DebtRatio'].astype(float) """
최종적으로 분석용 데이터는 아래와 같은 형태를 띈다. 여기에 각 방법론을 적용해보자.
방법론 1) Graham
def graham(df): graham_df = df[(df['ROA'] > 5) & (df['PBR'] > 0.2) & (df['DebtRatio'] < 50)].sort_values('PBR') return graham_df
아래와 같이 Graham 방법론으로 filtering 된 종목들을 확인해볼 수 있다.
방법론 2) NCAV
- 정석대로 코드화하면 아래와 같다. tight_ncav 는 "(현 자산 - 총 부채) > 시가총액" 조건을 적용한 것이고, loose_ncav 는 "(현 자산 - 총 부채) * 0.85 > 시가총액" 조건을 적용한 것이다. 순유동자산 측면에서 더 tight 한지 loose 한지로 변수명을 지정했다.
def ncav(df): filtered_df = df[(df['PER'] > 0.2) & (df['PER'] < 5) & (df['netIncome'] > 0)] tight_ncav = filtered_df[filtered_df['CurrentAssets'] - filtered_df['TotalLiabilities'] > filtered_df['MarketCap']] #loose_ncav = filtered_df[(filtered_df['CurrentAssets'] - filtered_df['TotalLiabilities']) * 0.85 > filtered_df['MarketCap']] return tight_ncav
연초에 비해 PER 지수가 낮은 종목이 줄어, 수집한 데이터 상에서 "PER > 0.2" 조건을 만족하는 종목이 없었다. 그래서 아래 예시는 "PER > 0.0 & PER < 5.0" 조건을 만족하는 종목을 추린 예시이다.
방법론 3) 3P Combo
- 위의 두 방법과 다른 점은 조건을 만족하는 종목을 추려서 반환해주는 것이 아니라, rank 화된 점수를 반환해주는 것이다. rank 가 낮은 종목부터 매수하여 몇 개 종목을 매수할 것인지는 사용하는 사람의 플랜에 따라 달라진다.
from scipy.stats import rankdata def combo_3p(df): per_rank = list(rankdata(list(df['PER']))) pbr_rank = list(rankdata(list(df['PBR']))) psr_rank = list(rankdata(list(df['PSR']))) ele = [x + y + z for x, y, z in zip(per_rank, pbr_rank, psr_rank)] # 따로 rank 값을 정규화하진 않았다. (rank 차이가 중요한 것이 아니라 순서만 활용하므로) df['Rank'] = ele df.sort_values(['Rank'], inplace=True) return df
위 코드를 적용해서 종목별 rank 를 구하면 아래와 같다.
방법론 4) PBR + GP/A
- 3P Combo 에서 사용한 코드와 유사하게 적용할 수 있다. 대신 조금 다른 부분은 PBR 은 낮을 수록 좋고, GP/A 는 높을 수록 좋다는 점.
def pbr_gpa(df): per_rank = list(rankdata(list(df['PBR']))) # 결과를 reverse 하기 위해 전체 데이터 갯수에서 rank 를 뺌 (rankdata 값은 전체 데이터 갯수보다 크지 않음) gpa_rank = list(len(df['GP/A']) - rankdata(list(df['GP/A'].astype(int)))) ele = [x + y for x, y in zip(per_rank, gpa_rank)] df['Rank2'] = ele df.sort_values(['Rank2'], inplace=True) return df
결과는 아래와 같다. 이것 역시 rank 가 낮은 종목부터 매수하는 방법이기 때문에 매수 전략에 맞게 적절히 활용하면 된다.
이렇게 퀀트 기법을 적용하면, 데이터에 기반하여 저평가된 종목들을 추릴 수 있다.
그렇다면 이 중에서 어떤 기법이 가장 좋다고 말할 수 있을까? 기법들이 추려내는 종목들 중 공통된 부분들을 최우선으로 매수하는 방법도 괜찮은 것 같다.
아니면 미래를 예측할 순 없지만 과거 데이터에 기반하여, 내가 매수하고자 하는 종목의 누적 수익률은 확인해볼 수 있다.
다음에 기회가 된다면 추린 종목들로 백테스팅하는 것도 다뤄보고자 한다.
'Programming' 카테고리의 다른 글
[Python] naver finance 에서 원하는 정보 긁어오기 Part 2. (한국편) (0) 2022.09.15 [Python] naver finance 에서 원하는 정보 긁어오기 Part 1. (한국편) (2) 2022.09.09 [Python] 퀀트 투자 기법 적용하기 Part 1. (미국편) (2) 2022.08.28 [Python] yahoo finance 에서 원하는 정보 긁어오기 (미국편) (2) 2022.08.24 [Python] Google spread sheet 로 데이터 관리하기 (3) 2022.08.21