퀀트

22. 켈리 베팅

만 기 2023. 1. 12. 13:36

 

켈리 베팅

퀀트 분석을 통해서 나온 승률과 손익비를 켈리 공식에 적용하여 최적의 비중을 잡는 베팅 방법. (승률 50% 이상일때만)

 

 

백테스팅 결과를 켈리 공식에 적용해서 비중 구하기

stats =

p = stats['Win Rate [%]']/100

q = 1 - p

b = (1 + stats['Avg. Trade [%]'] / 100)

f_optimal = (b * p - q) / b
# 0.008509040722422218

 

 

백테스팅에 적용하기

# 골든 크로스 매수 - 데드 크로스 매도
class SmaCross(Strategy):
    def init(self):
        price = self.data.Close
        self.ma1 = self.I(SMA, price, 120)
        self.ma2 = self.I(SMA, price, 180)

        self.signal = self.I(lambda: np.repeat(np.nan, len(self.data.Close)), scatter=True)

        self.portion = 0.00851

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.position.close()
            self.buy(size=self.portion)
        elif crossover(self.ma2, self.ma1):
            self.position.close()
    
    def buy(self, *args, **kwargs):
        self.signal[-1] = 1  # On buy, input 1 instead of existing NaN
        super().buy(*args, **kwargs)

# 005930 : 삼성전자
data = fdr.DataReader('005930')

bt = Backtest(data,
              SmaCross,
              cash=1000000,
              commission=.002,
              exclusive_orders=True)
stats = bt.run()
print(stats)
bt.plot()

 

 

전체 종목 및 다른 종목에도 적용해보기

from tqdm.notebook import tqdm


# 코스피 전 종목 주가 가져오기
kosdaq_list = fdr.StockListing('KOSDAQ')
stock_list = kosdaq_list.loc[:,['Name', 'Symbol']].values.tolist()


# 전 종목 주가를 딕셔너리에 저장
df_dict = dict()
for name, code in tqdm(stock_list):
    _df = fdr.DataReader(code)
    if len(_df) > 0 and 'Close' in _df.columns:
        df_dict[name] = _df


# 골든 크로스 매수 - 데드 크로스 매도 전략
class SmaCross(Strategy):
    def init(self):
        price = self.data.Close
        self.ma1 = self.I(SMA, price, 10)
        self.ma2 = self.I(SMA, price, 20)

        self.signal = self.I(lambda: np.repeat(np.nan, len(self.data.Close)), scatter=True)

        self.portion = 0.9999

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.position.close()
            self.buy(size=self.portion)
        elif crossover(self.ma2, self.ma1):
            self.position.close()
    
    def buy(self, *args, **kwargs):
        self.signal[-1] = 1  # On buy, input 1 instead of existing NaN
        super().buy(*args, **kwargs)


# 전 종목을 전략 백테스팅 후 승률 딕셔너리를 만들어서 저장
winrate_dict = dict()
for key, value in tqdm(df_dict.items()):
    data = value

    bt = Backtest(data,
                  SmaCross,
                  cash=1000000,
                  commission=.002,
                  exclusive_orders=True)
    stats = bt.run()

    winrate_dict[key] = stats['Win Rate [%]']


# 딕셔너리를 내림차순 정렬
sorted_winrate_dict = dict(sorted(winrate_dict.items(), key=lambda item: item[1], reverse=True))
  • dict.items() : 딕셔너리의 키와 값을 튜플로 묶어 리스트로 반환한다.
  • 딕셔너리 value로 내림차순 정렬 :
    • sorted(데이터, key, reverse) :
      • 딕셔너리의 키와 값을 묶기 위해 .items()로 넣어서 sorted결과가 리스트로 반환된다. 따라서 dict()로 sorted()를 다시 감싸준다.
      • key는 무엇을 기준으로 정렬할지. 딕셔너리key와 다름
      • reverse는 오름차순 또는 내림차순 결정
      • item=(키, 값)이 되어 lambda의 item[1]은 딕셔너리의 값을 의미

 

 

켈리 공식 함수화

def kelly_criterion_portion(stats):
    p = stats['Win Rate [%]'] / 100
    b = (1 + stats['Avg. Trade [%]'] / 100)
    q = (1-p)
    f_optimal = (b*p-q)/b
    
    return f_optimal