퀀트

27. 모든 주식에 대해 보유 기간별 수익률 검증하기

만 기 2023. 1. 19. 15:51

 

모든 주식에 대해 보유 기간별 수익률 검증하기 : 손절매가 필수적인 통계적 근거

 

데이터 준비

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]})');

 

결론

  • 승률은 낮지만 수익률이 있다. ⇒ 소수의 급등 종목
  • 수익률의 중간값보다 평균값이 높다. ⇒ 수익률 편차가 크다.

⇒ 오르지 않는 종목을 보유할 필요 없고, 급등하는 몇 안되는 종목들이 급등한다.