모든 주식에 대해 보유 기간별 수익률 검증하기 : 손절매가 필수적인 통계적 근거
데이터 준비
d = {}
# For문 이용하여 읽기- 주가, 시가총액, 매출액, 영업익, 순이익, 부채, 유동자산, 자본
for data_name in ['adj_close', 'mc', 'sales_ttm', 'op_ttm', 'ni_ttm', 'liab', 'asset_cur', 'eq']:
print(f"read csv {data_name} ...")
d[data_name] = pd.read_csv(f"./stock.{data_name}.csv", index_col=0, encoding='cp949', parse_dates=True)
print(f"done! {d[data_name].shape}")
# 자주 쓰는 데이터
mc = d['mc']
adj_close = d['adj_close']
자주 사용될 함수 만들기
1. midxs가 True인 인덱스에서 보유일 (hold_day)별로 수익률을 구해주는 함수
def get_all_return_df_by(d, midxs, hold_day_li=[1,2,3,5,10,20,40,60,120]):
return pd.concat([pd.Series(d[f'close_return_{hold_day}d'].stack(dropna=False).loc[midxs], name=f'{hold_day}일 보유') for hold_day in hold_day_li], axis=1)
- midxs : 멀티인덱스. 종목과 날짜
- .stack() : 컬럼을 인덱스로 바꿔줌(멀티인덱스), 시리즈로 반환된다.
2. 기본 통계정보(describe) 외, 퀀트 분석 결과(aggregation performance)를 출력해주는 함수
def describe_and_aggperf(ret_midxdf):
_desc_df = ret_midxdf.describe()
# 유효한 데이터 총 갯수(열 기준)
eachcols_nrecords = ret_midxdf.notna().sum(axis=0)
# 수익률 있는것만 모은 데이터, 수익률 없는것만 모은 데이터 저장
_win_df, _loss_df = ret_midxdf.applymap(lambda e: e if e>=0 else np.nan), ret_midxdf.applymap(lambda e: e if e<0 else np.nan)
# 통계 추가. 승률, 승리횟수, 승리수익평균, 승리수익중간값
_desc_df = _desc_df.append(pd.Series(((ret_midxdf >= 0).sum(axis=0) / eachcos_nrecords), name='win_rate'))
_desc_df = _desc_df.append(pd.Series(_win_df.count(axis=0), name='win_count'))
_desc_df = _desc_df.append(pd.Series(_win_df.mean(axis=0), name='win_return_mean'))
_desc_df = _desc_df.append(pd.Series(_win_df.median(axis=0), name='win_return_median'))
_desc_df = _desc_df.append(pd.Series(((ret_midxdf < 0).sum(axis=0) / eachcols_nrecords), name='loss_rate'))
_desc_df = _desc_df.append(pd.Series(_loss_df.count(axis=0), name='loss_count'))
_desc_df = _desc_df.append(pd.Series(_loss_df.mean(axis=0), name='loss_return_mean'))
_desc_df = _desc_df.append(pd.Series(_loss_df.median(axis=0), name='loss_return_median'))
return _desc_df
- .describe() : 평균, 최대, 최소, 4분위값 등 데이터 통계를 알려주는 함수
- .notna() : NaN, NaT, None 값이 있으면 False 아니면 True 로 반환
- .applymap() : Dataframe에 모든 요소 하나하나에 함수를 적용한다.
- lambda e: e if e>=0 else np.nan : e≥0 는 수익률이 있다 의 뜻. 나머지는 nan처리
3. 퀀트 분석 결과에 대해 boxplot 차트를 그려주는 함수
def draw_meanmed_boxplot(_df, _title=None, _figsize=(18,9)):
_ax = _df.boxplot(figsize=_figsize, showfliers=False, showmeans=True);
if _title:
_ax.set_title(_title, fontweight='bold');
legend_elements = [plt.Line2D([0], [0], marker='^', color='None', label='평균 값', markerfacecolor='g', markeredgecolor='None', markersize=15),
plt.Line2D([0], [0], color='gray', lw=2, label='중앙 값')]
_ax.legend(handles=legend_elements, loc='upper left');
# 그래프 스케일 설정
_ytmin, _ytmax = round(_ax.get_yticks()[0],1), round(_ax.get_yticks()[-1],1)
_gap = 0.1 if (_ytmax - _ytmin) > 1.6 else 0.05
_yticks = np.arange(_ytmin, _ytmax, _gap)
_yticklabels = list(map(lambda e: int(e),np.arange(100*_ytmin, 100*_ytmax, 100*_gap)))
_ax.set_yticks(_yticks)
_ax.set_yticklabels(_yticklabels)
_ax.axhline(0, linewidth=1)
return _ax
- map(함수, 리스트) : 리스트로부터 원소를 하나씩 꺼내서 함수를 적용시킨다
보유기간별 수익률 데이터 생성
# 퀀트 분석 결과를 저장할 딕셔너리
exp = {}
# 보유기간 별 데이터 생성
_sr_li = list()
for hold_day in [1, 2, 3, 5, 10, 20, 40, 60, 120, 240]:
_df = adj_close.iloc[:, ::hold_day] # 전체 날짜에서 보유기간 간격만큼의 날짜만
_prev = _df.shift(1, axis=1).replace(0, np.nan) # 보유기간만큼의 이전 날짜
_ret = ((_df / _prev) - 1).stack(dropna=False) # 수익률 계산. 멀티인덱스(시리즈)
# dropna = False로 NaN 값 유지시켜서 날짜 맞추기
# n일 수익률 데이터(시리즈)를 리스트에 추가
_sr_li.append((pd.Series(_ret.values, name=f'{hold_day}일_보유')))
# 수익률 리스트를 데이터프레임으로
exp_df = pd.DataFrame(_sr_li).T
함수에 넣고 결과 보기
# 통계, 집계 데이터 보기
describe_and_aggperf(exp_df)
# 박스 그래프 보기
draw_meanmed_boxplot(exp_df, f'코스피 코스닥 전 종목, 보유기간별 수익률 통계 ({adj_close.columns[0]} ~ {adj_close.columns[-1]})');
결론
- 승률은 낮지만 수익률이 있다. ⇒ 소수의 급등 종목
- 수익률의 중간값보다 평균값이 높다. ⇒ 수익률 편차가 크다.
⇒ 오르지 않는 종목을 보유할 필요 없고, 급등하는 몇 안되는 종목들이 급등한다.
'퀀트' 카테고리의 다른 글
29. 이평선 투자 전략 : 정배열 또는 역배열에 매수하는 경우 (0) | 2023.01.25 |
---|---|
28. 이평선 투자 전략 : 이평선의 위 또는 아래에서 매수하는 경우 (0) | 2023.01.20 |
26. 해리 마코위츠의 포트폴리오 이론(분산투자의 필요성) (0) | 2023.01.16 |
25. 레이 달리오의 올웨더 전략 (0) | 2023.01.15 |
24. 게리 안토나치의 듀얼 모멘텀 전략 (0) | 2023.01.14 |