# How to size your bets - The Kelly Rule

he Kelly rule has a long history in gambling because it provides guidance on how much to stake on each of an (infinite) sequence of bets with varying (but favorable) odds to maximize terminal wealth. It was published as A New Interpretation of the Information Rate in 1956 by John Kelly who was a colleague of Claude Shannon's at Bell Labs. He was intrigued by bets placed on candidates at the new quiz show The $64,000 Question, where a viewer on the west coast used the three-hour delay to obtain insider information about the winners. 

Kelly drew a connection to Shannon's information theory to solve for the bet that is optimal for long-term capital growth when the odds are favorable, but uncertainty remains. His rule maximizes logarithmic wealth as a function of the odds of success of each game, and includes implicit bankruptcy protection since log(0) is negative infinity so that a Kelly gambler would naturally avoid losing everything.

## Imports


```python
%matplotlib inline
from sympy import symbols, solve, log, diff
from scipy.optimize import minimize_scalar, newton, minimize
from scipy.integrate import quad
from scipy.stats import norm
import numpy as np
import pandas as pd
from numpy.linalg import inv
import matplotlib.pyplot as plt
from numpy.random import dirichlet
import warnings
```


```python
warnings.filterwarnings('ignore')
plt.style.use('fivethirtyeight')
np.random.seed(42)
```

## The optimal size of a bet

Kelly began by analyzing games with a binary win-lose outcome. The key variables are:
- b: The odds define the amount won for a \\$1 bet. Odds = 5/1 implies a \\$5 gain if the bet wins, plus recovery of the \\$1 capital.
- p: The probability defines the likelihood of a favorable outcome.
- f: The share of the current capital to bet.
- V: The value of the capital as a result of betting.

The Kelly rule aims to maximize the value's growth rate, G, of infinitely-repeated bets (see Chapter 5 for background).
$$G=\lim_{N\rightarrow\infty}=\frac{1}{N}\log\frac{V_N}{V_0}$$

We can maximize the rate of growth G by maximizing G with respect to f, as illustrated using sympy as follows:


```python
share, odds, probability = symbols('share odds probability')
Value = probability * log(1 + odds * share) + (1 - probability) * log(1 - share)
solve(diff(Value, share), share)
```




    [(odds*probability + probability - 1)/odds]




```python
f, p = symbols('f p')
y = p * log(1 + f) + (1 - p) * log(1 - f)
solve(diff(y, f), f)
```




    [2*p - 1]



## Get S&P 500 Data


```python
with pd.HDFStore('../../data/assets.h5') as store:
    sp500 = store['sp500/prices'].close
```

### Compute Returns & Standard Deviation


```python
annual_returns = sp500.resample('A').last().pct_change().to_frame('sp500')
```


```python
return_params = annual_returns.sp500.rolling(25).agg(['mean', 'std']).dropna()
```


```python
return_ci = (return_params[['mean']]
                .assign(lower=return_params['mean'].sub(return_params['std'].mul(2)))
                .assign(upper=return_params['mean'].add(return_params['std'].mul(2))))
```


```python
return_ci.plot(lw=2, figsize=(14, 8));
```

### Kelly Rule for a Single Asset - Index Returns

In a financial market context, both outcomes and alternatives are more complex, but the Kelly rule logic does still apply. It was made popular by Ed Thorp, who first applied it profitably to gambling (described in Beat the Dealer) and later started the successful hedge fund Princeton/Newport Partners.

With continuous outcomes, the growth rate of capital is defined by an integrate over the probability distribution of the different returns that can be optimized numerically.
We can solve this expression (see book) for the optimal f* using the `scipy.optimize` module:


```python
def norm_integral(f, mean, std):
    val, er = quad(lambda s: np.log(1 + f * s) * norm.pdf(s, mean, std), 
                               mean - 3 * std, 
                               mean + 3 * std)
    return -val
```


```python
def norm_dev_integral(f, mean, std):
    val, er = quad(lambda s: (s / (1 + f * s)) * norm.pdf(s, mean, std), m-3*std, mean+3*std)
    return val
```


```python
def get_kelly_share(data):
    solution = minimize_scalar(norm_integral, 
                        args=(data['mean'], data['std']), 
                        bounds=[0, 2], 
                        method='bounded') 
    return solution.x
```


```python
annual_returns['f'] = return_params.apply(get_kelly_share, axis=1)
```


```python
return_params.plot(subplots=True, lw=2, figsize=(14, 8));
```


```python
annual_returns.tail()
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>sp500</th>
      <th>f</th>
    </tr>
    <tr>
      <th>date</th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2014-12-31</th>
      <td>0.113906</td>
      <td>1.999996</td>
    </tr>
    <tr>
      <th>2015-12-31</th>
      <td>-0.007266</td>
      <td>1.999996</td>
    </tr>
    <tr>
      <th>2016-12-31</th>
      <td>0.095350</td>
      <td>1.999996</td>
    </tr>
    <tr>
      <th>2017-12-31</th>
      <td>0.194200</td>
      <td>1.999996</td>
    </tr>
    <tr>
      <th>2018-12-31</th>
      <td>0.014815</td>
      <td>1.999996</td>
    </tr>
  </tbody>
</table>
</div>



### Performance Evaluation


```python
(annual_returns[['sp500']]
 .assign(kelly=annual_returns.sp500.mul(annual_returns.f.shift()))
 .dropna()
 .loc['1900':]
 .add(1)
 .cumprod()
 .sub(1)
 .plot(lw=2));

```


```python
annual_returns.f.describe()
```




    count    205.000000
    mean       1.303795
    std        0.585540
    min        0.000003
    25%        0.830339
    50%        1.227349
    75%        1.999996
    max        1.999996
    Name: f, dtype: float64




```python
return_ci.head()
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>mean</th>
      <th>lower</th>
      <th>upper</th>
    </tr>
    <tr>
      <th>date</th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1814-12-31</th>
      <td>0.074059</td>
      <td>-0.426468</td>
      <td>0.574586</td>
    </tr>
    <tr>
      <th>1815-12-31</th>
      <td>0.070621</td>
      <td>-0.429199</td>
      <td>0.570442</td>
    </tr>
    <tr>
      <th>1816-12-31</th>
      <td>0.064872</td>
      <td>-0.436591</td>
      <td>0.566336</td>
    </tr>
    <tr>
      <th>1817-12-31</th>
      <td>0.075920</td>
      <td>-0.418596</td>
      <td>0.570436</td>
    </tr>
    <tr>
      <th>1818-12-31</th>
      <td>0.074465</td>
      <td>-0.419380</td>
      <td>0.568309</td>
    </tr>
  </tbody>
</table>
</div>



### Compute Kelly Fraction


```python
m = .058
s = .216
```


```python
# Option 1: minimize the expectation integral
sol = minimize_scalar(norm_integral, args=(m, s), bounds=[0., 2.], method='bounded')
print('Optimal Kelly fraction: {:.4f}'.format(sol.x))
```

    Optimal Kelly fraction: 1.1974



```python
# Option 2: take the derivative of the expectation and make it null
x0 = newton(norm_dev_integral, .1, args=(m, s))
print('Optimal Kelly fraction: {:.4f}'.format(x0))
```

    Optimal Kelly fraction: 1.1974


## Kelly Rule for Multiple Assets

We will use an example with various equities. [E. Chan (2008)](https://www.amazon.com/Quantitative-Trading-Build-Algorithmic-Business/dp/0470284889) illustrates how to arrive at a multi-asset application of the Kelly Rule, and that the result is equivalent to the (potentially levered) maximum Sharpe ratio portfolio from the mean-variance optimization. 

The computation involves the dot product of the precision matrix, which is the inverse of the covariance matrix, and the return matrix:


```python
with pd.HDFStore('../../data/assets.h5') as store:
    sp500_stocks = store['sp500/stocks'].index 
    prices = store['quandl/wiki/prices'].adj_close.unstack('ticker').filter(sp500_stocks)
```


```python
prices.info()
```

    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 14277 entries, 1962-01-02 to 2018-03-27
    Columns: 501 entries, MMM to ZTS
    dtypes: float64(501)
    memory usage: 54.7 MB



```python
monthly_returns = prices.loc['1988':'2017'].resample('M').last().pct_change().dropna(how='all').dropna(axis=1)
stocks = monthly_returns.columns
monthly_returns.info()
```

    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 359 entries, 1988-02-29 to 2017-12-31
    Freq: M
    Columns: 217 entries, MMM to XRX
    dtypes: float64(217)
    memory usage: 611.4 KB


### Compute Precision Matrix


```python
cov = monthly_returns.cov()
precision_matrix = pd.DataFrame(inv(cov), index=stocks, columns=stocks)
```


```python
kelly_allocation = monthly_returns.mean().dot(precision_matrix)
```


```python
kelly_allocation.describe()
```




    count    217.000000
    mean       0.213979
    std        3.493734
    min      -10.840354
    25%       -1.845360
    50%        0.002192
    75%        1.990041
    max       11.316506
    dtype: float64




```python
kelly_allocation.sum()
```




    46.433472367624404



### Largest Portfolio Allocation

The plot shows the tickers that receive an allocation weight > 5x their value:


```python
kelly_allocation[kelly_allocation.abs()>5].sort_values(ascending=False).plot.barh(figsize=(8, 10))
plt.yticks(fontsize=12)
plt.tight_layout()
```

### Performance vs SP500

The Kelly rule does really well. But it has also been computed from historical data..


```python
ax = monthly_returns.loc['2010':].mul(kelly_allocation.div(kelly_allocation.sum())).sum(1).to_frame('Kelly').add(1).cumprod().sub(1).plot();
sp500.loc[monthly_returns.loc['2010':].index].pct_change().add(1).cumprod().sub(1).to_frame('SP500').plot(ax=ax, legend=True);
```


```python

```

25a9758724516be6bddeb10248103efba3821760
ipynb
Jupyter Notebook
Chapter05/05_kelly/kelly_rule.ipynb
kksonge/Hands-On-Machine-Learning-for-Algorithmic-Trading
01516a70f777c78673feaac16a477b457d0381b4
MIT
2019-01-06T22:44:31.000Z
2022-03-30T14:47:33.000Z
Chapter05/05_kelly/kelly_rule.ipynb
kksonge/Hands-On-Machine-Learning-for-Algorithmic-Trading
01516a70f777c78673feaac16a477b457d0381b4
MIT
2019-01-05T08:34:25.000Z
2021-11-15T01:11:54.000Z
Chapter05/05_kelly/kelly_rule.ipynb
Nnamdi-sys/Machine-Learning-for-Algo-Trading
9082adece2bbdc6375eb58e2253addf0cbbdfd07
MIT
2018-12-09T00:09:50.000Z
2022-03-18T10:21:13.000Z
Qwen/Qwen-72B
1. YES
2. YES