276 lines
6.9 KiB
Plaintext
276 lines
6.9 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "abfc4950",
|
|
"metadata": {},
|
|
"source": [
|
|
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8a8caa4f",
|
|
"metadata": {},
|
|
"source": [
|
|
"This code calculates the Kelly fraction, the optimal leverage for maximizing long-term growth, using historical stock returns. It fetches annual returns for the S&P 500 index, computes rolling statistics, and applies a numerical integration method to optimize the leverage. The code also visualizes the cumulative compounded returns using the Kelly strategy. This is useful in practice for portfolio management, risk management, and enhancing investment performance through optimized leverage."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "bee14bab",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from openbb_terminal.sdk import openbb"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "cf2ef711",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import numpy as np\n",
|
|
"from scipy.optimize import minimize_scalar\n",
|
|
"from scipy.integrate import quad\n",
|
|
"from scipy.stats import norm"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "174c0343",
|
|
"metadata": {},
|
|
"source": [
|
|
"Fetch annual returns for the S&P 500 index since 1950"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "8cebb20d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"annual_returns = (\n",
|
|
" openbb.economy.index([\"^GSPC\"], start_date=\"1950-01-01\", column=\"Close\")\n",
|
|
" .resample(\"A\")\n",
|
|
" .last()\n",
|
|
" .pct_change()\n",
|
|
" .dropna()\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "283545ce",
|
|
"metadata": {},
|
|
"source": [
|
|
"Compute rolling mean and standard deviation over a 25-year window"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "c5798723",
|
|
"metadata": {
|
|
"lines_to_next_cell": 1
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"return_params = annual_returns[\"^GSPC\"].rolling(25).agg([\"mean\", \"std\"]).dropna()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "eca2ef30",
|
|
"metadata": {},
|
|
"source": [
|
|
"Define a function to calculate the negative value of the expected log return"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "8dd7072f",
|
|
"metadata": {
|
|
"lines_to_next_cell": 1
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def norm_integral(f, mean, std):\n",
|
|
" \"\"\"Calculates the negative expected log return\n",
|
|
" \n",
|
|
" Parameters\n",
|
|
" ----------\n",
|
|
" f : float\n",
|
|
" Leverage factor\n",
|
|
" mean : float\n",
|
|
" Mean return\n",
|
|
" std : float\n",
|
|
" Standard deviation of returns\n",
|
|
" \n",
|
|
" Returns\n",
|
|
" -------\n",
|
|
" float\n",
|
|
" Negative expected log return\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" val, er = quad(\n",
|
|
" lambda s: np.log(1 + f * s) * norm.pdf(s, mean, std),\n",
|
|
" mean - 3 * std,\n",
|
|
" mean + 3 * std,\n",
|
|
" )\n",
|
|
" return -val"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8df71ad1",
|
|
"metadata": {},
|
|
"source": [
|
|
"Define a function to optimize the Kelly fraction using the minimize_scalar method"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "3d3d8894",
|
|
"metadata": {
|
|
"lines_to_next_cell": 1
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def get_kelly(data):\n",
|
|
" \"\"\"Optimizes the Kelly fraction\n",
|
|
" \n",
|
|
" Parameters\n",
|
|
" ----------\n",
|
|
" data : pd.Series\n",
|
|
" Contains mean and standard deviation of returns\n",
|
|
" \n",
|
|
" Returns\n",
|
|
" -------\n",
|
|
" float\n",
|
|
" Optimal Kelly fraction\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" solution = minimize_scalar(\n",
|
|
" norm_integral, args=(data[\"mean\"], data[\"std\"]), bounds=[0, 2], method=\"bounded\"\n",
|
|
" )\n",
|
|
" return solution.x"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "03519c41",
|
|
"metadata": {},
|
|
"source": [
|
|
"Calculate the Kelly fraction for each rolling window and add it to the annual returns DataFrame"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "ff591e28",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"annual_returns['f'] = return_params.apply(get_kelly, axis=1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "bce1e53b",
|
|
"metadata": {},
|
|
"source": [
|
|
"Visualize the cumulative compounded returns using the Kelly strategy"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "681f13ca",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"(\n",
|
|
" annual_returns[[\"^GSPC\"]]\n",
|
|
" .assign(kelly=annual_returns[\"^GSPC\"].mul(annual_returns.f.shift()))\n",
|
|
" .dropna()\n",
|
|
" .loc[\"1900\":]\n",
|
|
" .add(1)\n",
|
|
" .cumprod()\n",
|
|
" .sub(1)\n",
|
|
" .plot(lw=2)\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "80eacb96",
|
|
"metadata": {},
|
|
"source": [
|
|
"Pick an arbitrary point for mean and standard deviation to calculate optimal Kelly fraction"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "126a1da8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"m = .058\n",
|
|
"s = .216"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8d61618b",
|
|
"metadata": {},
|
|
"source": [
|
|
"Optimize the Kelly fraction for the given mean and standard deviation"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "60235902",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"sol = minimize_scalar(norm_integral, args=(m, s), bounds=[0.0, 2.0], method=\"bounded\")\n",
|
|
"print(\"Optimal Kelly fraction: {:.4f}\".format(sol.x))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "687a9a94",
|
|
"metadata": {},
|
|
"source": [
|
|
"This formula can result in Kelly fractions higher than 1. In this case, it is theoretically advantageous to use leverage to purchase additional securities on margin."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3ce91624",
|
|
"metadata": {},
|
|
"source": [
|
|
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"jupytext": {
|
|
"cell_metadata_filter": "-all",
|
|
"main_language": "python",
|
|
"notebook_metadata_filter": "-all"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|