├── pqn.png ├── .gitignore ├── README.md ├── how_to_compute_drawdown_on_an_investment.ipynb ├── how_to_use_the_sharpe_ratio_for_riskadjusted_returns.ipynb ├── evaluate_a_real_trading_strategy_with_python_and_pandas.ipynb ├── a_simple_stat_arb_strategy_you_can_trade_now.ipynb ├── how_to_compute_volatility_6_ways_most_people_don’t_know.ipynb ├── how_to_simulate_stock_prices_with_python.ipynb ├── value_at_risk_manage_your_portfolios_risk.ipynb └── how_to_tell_if_options_are_cheap_with_volatility_cones.ipynb /pqn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyquantnews/PyQuantNewsletter/HEAD/pqn.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Jupyter Notebook 2 | .ipynb_checkpoints 3 | 4 | # Mac stuff 5 | .DS_Store 6 | .virtual_documents 7 | 8 | # Python 9 | matplotlibrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code for the PyQuant Newsletter 2 | _Join traders, quants, and complete beginners using Python for algorithmic trading, market data analysis, and quant finance._ 3 | 4 | ## Overview 5 | This repository contains the code for the **PyQuant Newsletter**. Each issue focuses on practical, implementation-ready Python techniques for quantitative finance, algorithmic trading, factor research, portfolio construction, and data engineering. You can [subscribe to the PyQuant Newsletter on Substack free](https://pyquantnews.substack.com/subscribe). 6 | 7 | ## Why subscribe to the free PyQuant Newsletter 8 | 9 | The PyQuant Newsletter gives you applied quant finance in a compact format designed for working professionals: 10 | 11 | - **Hands-on code**: Each issue includes runnable Python examples for immediate use. 12 | - **Actionable quant ideas**: Factor signals, trading edges, vol modeling, portfolio techniques, data pipelines. 13 | - **Practical focus**: Real workflows used by quants, traders, and data scientists. 14 | - **Time-efficient**: Designed so readers can extract value in ~5 minutes. 15 | - **Trusted by practitioners**: Read by tens of thousands of quants, traders, engineers, and analysts. 16 | 17 | ## Alpha Lab Membership 18 | 19 | An [Alpha Lab membership](https://pyquantnews.substack.com/subscribe) is for members who want access to backtested strategies and higher-touch interaction. 20 | 21 | #### Backtested Strategies 22 | Every month Alpha Lab members receive a full research deep dive, including: 23 | - Python code for signal construction 24 | - Backtests and performance analysis 25 | - Interpretation of edge, risk, and failure modes 26 | - A reproducible research notebook 27 | 28 | #### Upcoming strategies include: 29 | - **Reversion Strength** — identifies exhaustion points where price is likely to revert 30 | - **Trend Persistence** — rewards assets with sustained directional strength 31 | - **Stability Premium** — prioritizes assets with persistent, lower-volatility behavior 32 | 33 | #### Start New Chat Threads 34 | Alpha Lab members can initiate new discussion threads inside the private chat environment, enabling: 35 | - Direct Q&A 36 | - Strategy-specific help 37 | - Environment setup guidance 38 | - More detailed and open-ended discussions 39 | 40 | ## Paid Membership Benefits 41 | 42 | You can support also PyQuant News and get deeper support, updated code, and access to private discussions through a [paid membership](https://pyquantnews.substack.com/subscribe). 43 | 44 | #### Private Code Support 45 | Access to the **Substack Chat**, a private channel where members can get personalized help with: 46 | - Python code issues 47 | - Strategy design 48 | - Backtesting workflows 49 | - Execution logic 50 | - Troubleshooting library breaks and environment issues 51 | 52 | > Note: Only **Alpha Lab** members can start new chat threads. Paid members can reply and participate. 53 | 54 | #### Updated Code 55 | Python libraries evolve, dependencies break, and tutorials need maintenance. Paid members receive: 56 | - Updated notebooks 57 | - Rewritten code when APIs change 58 | - Fixes for breaking library updates 59 | - Continuously maintained examples that keep running over time 60 | 61 | #### Meaningful Discussions 62 | Every newsletter ends with a **members-only comments section**. 63 | Paid members get: 64 | - Direct interaction 65 | - Thoughtful replies 66 | - Deeper discussions around implementation and research decisions 67 | 68 | ## Who this is for 69 | The newsletter and membership benefits serve: 70 | - Quants and quant-curious developers 71 | - Systematic traders 72 | - Data scientists working with market data 73 | - Python programmers learning quant techniques 74 | - Researchers exploring factor models and trading strategies 75 | 76 | If you find this code useful, consider subscribing to receive future issues and member-only benefits. 77 | -------------------------------------------------------------------------------- /how_to_compute_drawdown_on_an_investment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "e7ab5fbe", 6 | "metadata": {}, 7 | "source": [ 8 | "
PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk.
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "8a8ad335", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation\n", 17 | "Install the libraries needed to download market data, compute vectorized metrics, and render plots so the notebook runs end to end." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "id": "95ee3b4e", 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "!pip install yfinance pandas numpy matplotlib" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "id": "7ed921e1", 33 | "metadata": {}, 34 | "source": [ 35 | "yfinance gives us quick access to historical prices, pandas handles time series, numpy powers fast array ops, and matplotlib lets pandas render the underwater charts. Installing them up front avoids environment surprises when we get to plotting and rolling calculations." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "id": "4907e200", 41 | "metadata": {}, 42 | "source": [ 43 | "## Imports and setup\n", 44 | "We use yfinance to download SPY price history for the study period, and numpy for vectorized operations used inside the drawdown calculations." 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "id": "6a1541ab", 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "import yfinance as yf\n", 55 | "import numpy as np" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "id": "1be9dbe5", 61 | "metadata": {}, 62 | "source": [ 63 | "Keeping imports minimal mirrors production pipelines where dependencies are kept lean and predictable. yfinance is fine for tutorials; in production you would typically swap in a data vendor but keep the same numpy-based logic." 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "id": "f29c65dc", 69 | "metadata": {}, 70 | "source": [ 71 | "## Fetch price data and returns\n", 72 | "Pull SPY daily prices for the sample window and transform them into simple daily returns, which are the input to our drawdown functions." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "8538fd86", 79 | "metadata": { 80 | "lines_to_next_cell": 1 81 | }, 82 | "outputs": [], 83 | "source": [ 84 | "data = yf.download(\"SPY\", start=\"2020-01-01\", end=\"2022-07-31\")\n", 85 | "returns = data.Close.pct_change()" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "ccf2a845", 91 | "metadata": {}, 92 | "source": [ 93 | "Using adjusted close aligns with what we care about: investable returns including dividends and splits. pct_change introduces a leading NaN, which is expected and handled later; the important part is we’re creating a clean, noncumulative return stream that we can roll up into an equity curve." 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "d982a9de", 99 | "metadata": {}, 100 | "source": [ 101 | "## Define drawdown utilities and metrics\n", 102 | "Implement a vectorized drawdown series and a companion max drawdown function so we can reuse the same logic in backtests, dashboards, and rolling risk checks." 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "id": "ac127d46", 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "def drawdown(returns):\n", 113 | " \"\"\"Determines the drawdown\n", 114 | "\n", 115 | " Parameters\n", 116 | " ----------\n", 117 | " returns : pd.Series\n", 118 | " Daily returns of an asset, noncumulative\n", 119 | "\n", 120 | " Returns\n", 121 | " -------\n", 122 | " drawdown : pd.Series\n", 123 | " \"\"\"\n", 124 | " returns.fillna(0.0, inplace=True)\n", 125 | "\n", 126 | " cumulative = (returns + 1).cumprod()\n", 127 | "\n", 128 | " running_max = np.maximum.accumulate(cumulative)\n", 129 | "\n", 130 | " return (cumulative - running_max) / running_max" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "id": "a065558e", 137 | "metadata": { 138 | "lines_to_next_cell": 1 139 | }, 140 | "outputs": [], 141 | "source": [ 142 | "def max_drawdown(returns):\n", 143 | " \"\"\"Determines the maximum drawdown\n", 144 | "\n", 145 | " Parameters\n", 146 | " ----------\n", 147 | " returns : pd.Series\n", 148 | " Daily returns of an asset, noncumulative\n", 149 | "\n", 150 | " Returns\n", 151 | " -------\n", 152 | " max_drawdown : float\n", 153 | " \"\"\"\n", 154 | " return np.min(drawdown(returns))" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "id": "b2233c38", 160 | "metadata": {}, 161 | "source": [ 162 | "The drawdown series tracks how far underwater we are relative to the running peak, which is how pros experience risk day to day. Filling NaNs with zero avoids contaminating the cumulative product at the start; note this mutates the input Series in place, so pass a copy if you need the original untouched. The minimum of the underwater series is the max drawdown, the single pain number allocators and risk teams anchor on." 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "c6427782", 168 | "metadata": {}, 169 | "source": [ 170 | "## Plot underwater and rolling risk\n", 171 | "Visualize the full-sample underwater curve, then a 30-day rolling max drawdown to spot regime shifts and trigger de-risking rules earlier." 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "id": "d1d734ee", 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [ 181 | "drawdown(returns).plot(kind=\"area\", color=\"salmon\", alpha=0.5)" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "id": "35c27bfe", 188 | "metadata": {}, 189 | "outputs": [], 190 | "source": [ 191 | "returns.rolling(30).apply(max_drawdown).plot(\n", 192 | " kind=\"area\",\n", 193 | " color=\"salmon\",\n", 194 | " alpha=0.5,\n", 195 | ")" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "id": "b2890ed0", 201 | "metadata": {}, 202 | "source": [ 203 | "The underwater area chart shows the depth and duration of capital losses, which is more decision-relevant than average return when markets stress. Rolling max drawdown highlights localized pockets of pain that get averaged away in full-sample stats, making it a practical alert for position sizing and pause rules. In production, this series feeds a dashboard and simple thresholds to keep risk within cash and mandate limits." 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "id": "93b7ceeb", 209 | "metadata": {}, 210 | "source": [ 211 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "id": "e73bdad9-aaca-44a3-8302-2ca9bc14fce8", 218 | "metadata": {}, 219 | "outputs": [], 220 | "source": [] 221 | } 222 | ], 223 | "metadata": { 224 | "jupytext": { 225 | "cell_metadata_filter": "-all", 226 | "main_language": "python", 227 | "notebook_metadata_filter": "-all" 228 | }, 229 | "kernelspec": { 230 | "display_name": "Python 3 (ipykernel)", 231 | "language": "python", 232 | "name": "python3" 233 | }, 234 | "language_info": { 235 | "codemirror_mode": { 236 | "name": "ipython", 237 | "version": 3 238 | }, 239 | "file_extension": ".py", 240 | "mimetype": "text/x-python", 241 | "name": "python", 242 | "nbconvert_exporter": "python", 243 | "pygments_lexer": "ipython3", 244 | "version": "3.12.9" 245 | } 246 | }, 247 | "nbformat": 4, 248 | "nbformat_minor": 5 249 | } 250 | -------------------------------------------------------------------------------- /how_to_use_the_sharpe_ratio_for_riskadjusted_returns.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "e771ac8a", 6 | "metadata": {}, 7 | "source": [ 8 | "
PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk.
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "11d9e861", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "eb0a0f05", 22 | "metadata": {}, 23 | "source": [ 24 | "Install the libraries we need to fetch market data, compute statistics, and visualize results so this notebook runs cleanly anywhere." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "1e52e486", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "!pip install yfinance pandas numpy matplotlib" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "728f4093", 40 | "metadata": {}, 41 | "source": [ 42 | "## Imports and setup" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "4e6a8342", 48 | "metadata": {}, 49 | "source": [ 50 | "We use yfinance to pull historical prices, numpy for fast numerical operations such as annualization, and matplotlib.pyplot to visualize Sharpe behavior over time." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "id": "fbb1d5d0", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "import yfinance as yf\n", 61 | "import numpy as np\n", 62 | "import matplotlib.pyplot as plt" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "fb398b3b", 68 | "metadata": {}, 69 | "source": [ 70 | "Keeping imports minimal helps us focus on the Sharpe workflow without extra dependencies. This mirrors what pros do in quick analyses: load prices, compute stats, and plot diagnostics. It also makes results easier to reproduce across machines and environments." 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "id": "467fa578", 76 | "metadata": {}, 77 | "source": [ 78 | "## Download and prepare daily returns" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "id": "07e77e22", 84 | "metadata": {}, 85 | "source": [ 86 | "Pull adjusted close prices for SPY and AAPL over a fixed horizon so we compare assets on the same calendar and sampling frequency." 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "id": "bde52d3c", 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "data = yf.download([\"SPY\", \"AAPL\"], start=\"2020-01-01\", end=\"2024-12-31\")" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "id": "8b457731", 102 | "metadata": {}, 103 | "source": [ 104 | "Using a single source avoids mismatched calendars and survivorship issues that skew risk metrics. Adjusted prices account for splits and dividends, which is important when you want returns that reflect what holders experienced. Consistent sourcing reduces noisy differences that would otherwise leak into Sharpe comparisons." 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "id": "4af8620d", 110 | "metadata": {}, 111 | "source": [ 112 | "Convert prices to noncumulative daily returns using close-to-close changes, which is the correct input for Sharpe." 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "id": "75dfbad8", 119 | "metadata": { 120 | "lines_to_next_cell": 1 121 | }, 122 | "outputs": [], 123 | "source": [ 124 | "closes = data.Close\n", 125 | "spy_returns = closes.SPY.pct_change().dropna()\n", 126 | "aapl_returns = closes.AAPL.pct_change().dropna()" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "id": "326d47af", 132 | "metadata": {}, 133 | "source": [ 134 | "We compute simple daily returns because Sharpe expects a stream of noncumulative observations; the difference versus log returns is negligible at daily horizons. Dropping missing values prevents artificial spikes or divide-by-zero artifacts that can inflate volatility. This gives us a clean, aligned return series for each asset." 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "0c5e7da2", 140 | "metadata": {}, 141 | "source": [ 142 | "## Define and compute Sharpe ratios" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "id": "57fb4be9", 148 | "metadata": {}, 149 | "source": [ 150 | "Define a helper that computes an annualized Sharpe ratio from daily returns, with an optional constant daily cash adjustment for excess returns." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "id": "1308284a", 157 | "metadata": { 158 | "lines_to_next_cell": 1 159 | }, 160 | "outputs": [], 161 | "source": [ 162 | "def sharpe_ratio(returns, adjustment_factor=0.0):\n", 163 | " \"\"\"\n", 164 | " Determines the Sharpe ratio of a strategy.\n", 165 | "\n", 166 | " Parameters\n", 167 | " ----------\n", 168 | " returns : pd.Series or np.ndarray\n", 169 | " Daily returns of the strategy, noncumulative.\n", 170 | " adjustment_factor : int, float\n", 171 | " Constant daily benchmark return throughout the period.\n", 172 | "\n", 173 | " Returns\n", 174 | " -------\n", 175 | " sharpe_ratio : float\n", 176 | "\n", 177 | " Note\n", 178 | " -----\n", 179 | " See https://en.wikipedia.org/wiki/Sharpe_ratio for more details.\n", 180 | " \"\"\"\n", 181 | " returns_risk_adj = returns - adjustment_factor\n", 182 | " return (\n", 183 | " returns_risk_adj.mean() / returns_risk_adj.std()\n", 184 | " ) * np.sqrt(252)" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "id": "9e7ff189", 190 | "metadata": {}, 191 | "source": [ 192 | "This implementation uses the sample standard deviation (pandas’ default) on noncumulative returns and annualizes with sqrt(252), matching common practice on U.S. trading days. In production, we would subtract a time-varying daily cash rate rather than a constant when rates move. The function enforces the “excess return per unit of volatility” idea that lets us compare assets fairly." 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "id": "305a2595", 198 | "metadata": {}, 199 | "source": [ 200 | "Compute full-sample Sharpe ratios for SPY and AAPL to benchmark their risk-adjusted performance over the same window." 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "id": "b7637bdd", 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "sharpe_ratio(spy_returns)" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": null, 216 | "id": "44e5cdd5", 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [ 220 | "sharpe_ratio(aapl_returns)" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "id": "03e1b134", 226 | "metadata": {}, 227 | "source": [ 228 | "Looking at full-sample Sharpe gives a quick first pass on which asset delivered more return per unit of risk, unlevered and on identical sampling. This avoids the “bigger CAGR wins” trap by normalizing for volatility and frequency. Treat these as starting points before you inspect stability through time." 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "id": "d700d765", 234 | "metadata": {}, 235 | "source": [ 236 | "## Visualize rolling Sharpe and differences" 237 | ] 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "id": "f0e9f692", 242 | "metadata": {}, 243 | "source": [ 244 | "Track 30-day rolling Sharpe for AAPL, then plot its distribution and the rolling Sharpe difference versus SPY to see stability and relative edge." 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": null, 250 | "id": "ec0e039a", 251 | "metadata": {}, 252 | "outputs": [], 253 | "source": [ 254 | "aapl_returns.rolling(30).apply(sharpe_ratio).plot()" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "id": "e16c1491-3650-422a-a2bf-14240e8b04a4", 261 | "metadata": {}, 262 | "outputs": [], 263 | "source": [ 264 | "aapl_returns.rolling(30).apply(sharpe_ratio).hist(bins=50)" 265 | ] 266 | }, 267 | { 268 | "cell_type": "code", 269 | "execution_count": null, 270 | "id": "c5b942b2-293e-47a2-92b0-72c0e94cb704", 271 | "metadata": {}, 272 | "outputs": [], 273 | "source": [ 274 | "(\n", 275 | " aapl_returns.rolling(30).apply(sharpe_ratio)\n", 276 | " - spy_returns.rolling(30).apply(sharpe_ratio)\n", 277 | ").hist(bins=50)" 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "id": "0a014e70", 283 | "metadata": {}, 284 | "source": [ 285 | "Rolling windows reveal regime shifts that a single full-period number hides, which is how desks set guardrails and monitor drift. The histograms show how often AAPL’s efficiency clusters at certain levels and when it outperforms SPY on a risk-adjusted basis. Short windows can be noisy, so in real reviews we also test multiple horizons and subtract a realistic daily cash rate when conditions change." 286 | ] 287 | }, 288 | { 289 | "cell_type": "markdown", 290 | "id": "44b1756c", 291 | "metadata": {}, 292 | "source": [ 293 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 294 | ] 295 | } 296 | ], 297 | "metadata": { 298 | "jupytext": { 299 | "cell_metadata_filter": "-all", 300 | "main_language": "python", 301 | "notebook_metadata_filter": "-all" 302 | }, 303 | "kernelspec": { 304 | "display_name": "Python 3 (ipykernel)", 305 | "language": "python", 306 | "name": "python3" 307 | }, 308 | "language_info": { 309 | "codemirror_mode": { 310 | "name": "ipython", 311 | "version": 3 312 | }, 313 | "file_extension": ".py", 314 | "mimetype": "text/x-python", 315 | "name": "python", 316 | "nbconvert_exporter": "python", 317 | "pygments_lexer": "ipython3", 318 | "version": "3.12.9" 319 | } 320 | }, 321 | "nbformat": 4, 322 | "nbformat_minor": 5 323 | } 324 | -------------------------------------------------------------------------------- /evaluate_a_real_trading_strategy_with_python_and_pandas.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b68b729a", 6 | "metadata": {}, 7 | "source": [ 8 | "
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "7a93132c", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "fbf603bb", 22 | "metadata": {}, 23 | "source": [ 24 | "Install the required Python packages so the notebook runs end-to-end in a fresh environment. This ensures everyone tests the same versions when measuring a small calendar edge." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "ff7e4199", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "!pip install yfinance pandas numpy matplotlib" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "641fa05a", 40 | "metadata": {}, 41 | "source": [ 42 | "These wheels install quickly on standard CPython via pip. For reproducibility, consider pinning versions in a requirements.txt when you move from exploration to backtesting. No non-standard system dependencies are needed for this notebook." 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "7f868274", 48 | "metadata": {}, 49 | "source": [ 50 | "## Imports and setup" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "id": "2367b3be", 56 | "metadata": {}, 57 | "source": [ 58 | "We import matplotlib.pyplot for plotting, pandas for time‑series manipulation, numpy for vectorized math and log returns, and yfinance to download historical TLT prices. This minimal stack keeps the focus on testing a calendar hypothesis rather than tooling." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "id": "ba9386e3", 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "import matplotlib.pyplot as plt\n", 69 | "import pandas as pd\n", 70 | "import numpy as np\n", 71 | "import yfinance as yf" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "id": "953d9fc4", 77 | "metadata": {}, 78 | "source": [ 79 | "Keeping the stack small reduces chances of hidden defaults that skew results. Matplotlib’s stateful interface will render plots at the end when we call plt.show(), which keeps notebooks tidy. yfinance retrieves split/dividend-adjusted series we will treat as our baseline." 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "id": "ab0b43e7", 85 | "metadata": {}, 86 | "source": [ 87 | "## Download data and build features" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "id": "1be8bfb9", 93 | "metadata": {}, 94 | "source": [ 95 | "Fetch daily TLT prices from Yahoo Finance to cover two decades of regimes for a robust seasonality check. The long sample lets us see whether month-end strength persists across different rate cycles." 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "ea1aec3d", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "tlt = yf.download(\"TLT\", start=\"2002-01-01\", end=\"2022-06-30\")" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "id": "e6770da8", 111 | "metadata": {}, 112 | "source": [ 113 | "Yahoo’s API returns a DataFrame with Open/High/Low/Close/Adj Close and a DateTime index. Using ETF data avoids survivorship bias from delisted bonds, though vendor adjustments can differ from your broker. Treat this as a quick scan; you can swap in official vendor data once the signal earns more work." 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "id": "9827125c", 119 | "metadata": {}, 120 | "source": [ 121 | "Compute daily log returns and attach calendar labels (day of month and year), then aggregate mean return by calendar day. This frames the problem as an event study across months." 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "id": "d3c82fb2", 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "tlt[\"log_return\"] = np.log(tlt[\"Adj Close\"] / tlt[\"Adj Close\"].shift(1))\n", 132 | "tlt[\"day_of_month\"] = tlt.index.day\n", 133 | "tlt[\"year\"] = tlt.index.year" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "id": "8d556bbf", 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "grouped_by_day = tlt.groupby(\"day_of_month\")[\"log_return\"].mean()" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "id": "9188c11b", 149 | "metadata": {}, 150 | "source": [ 151 | "Log returns add across time and handle compounding cleanly, which makes later cumulation and comparisons less fragile. Grouping by the calendar day gives us a cross-month average without peeking into the future because each day uses only its own prior close. Short and long months contribute whatever days they have, so the average reflects actual trading calendars rather than an imposed template." 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "id": "d35e9436", 157 | "metadata": {}, 158 | "source": [ 159 | "## Define month-end strategy proxy" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "id": "bed72eac", 165 | "metadata": {}, 166 | "source": [ 167 | "Build a simple diagnostic spread: sum of returns in the last calendar week minus the first calendar week. It approximates buying into month-end strength and giving it back after the turn." 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "id": "d4116ca8", 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [ 177 | "tlt[\"first_week_returns\"] = 0.0\n", 178 | "tlt.loc[tlt.day_of_month <= 7, \"first_week_returns\"] = tlt[\n", 179 | " tlt.day_of_month <= 7\n", 180 | "][\"log_return\"]" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "fc0de820", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "tlt[\"last_week_returns\"] = 0.0\n", 191 | "tlt.loc[tlt.day_of_month >= 23, \"last_week_returns\"] = tlt[\n", 192 | " tlt.day_of_month >= 23\n", 193 | "][\"log_return\"]" 194 | ] 195 | }, 196 | { 197 | "cell_type": "code", 198 | "execution_count": null, 199 | "id": "43ceb859", 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "tlt[\"last_week_less_first_week\"] = (\n", 204 | " tlt[\"last_week_returns\"] - tlt[\"first_week_returns\"]\n", 205 | ")" 206 | ] 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "id": "eac825ae", 211 | "metadata": {}, 212 | "source": [ 213 | "This is not an executable PnL yet; it’s a clean indicator of relative strength around the boundary. The definitions (<=7 and >=23) sidestep exact month lengths and exchange holidays while keeping the effect focused. If the spread is positive on average and reasonably stable, it justifies deeper work with precise trading days and costs." 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "id": "52da0a2c", 219 | "metadata": {}, 220 | "source": [ 221 | "## Visualize results and check stability" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "id": "df1cf7cc", 227 | "metadata": {}, 228 | "source": [ 229 | "Chart average log returns by calendar day to see where any excess clusters. Visual inspection quickly tells us whether the edge lives near the end or start." 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": null, 235 | "id": "559133b5", 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [ 239 | "grouped_by_day.plot.bar(\n", 240 | " title=\"Mean Log Returns by Calendar Day of Month\"\n", 241 | ")" 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "id": "6bb47041", 247 | "metadata": {}, 248 | "source": [ 249 | "Bars near the final trading days should stand out if flows lift prices into close, while early-month bars may dip if that strength mean-reverts. Plotting the average by day is the fastest way to validate the hypothesis before building a backtester. If nothing shows here, a more complex model probably will not rescue it." 250 | ] 251 | }, 252 | { 253 | "cell_type": "markdown", 254 | "id": "660e9ac8", 255 | "metadata": {}, 256 | "source": [ 257 | "Evaluate persistence with three views: yearly average of the spread, yearly cumulative contribution, and the full-sample cumulative path." 258 | ] 259 | }, 260 | { 261 | "cell_type": "code", 262 | "execution_count": null, 263 | "id": "5b75fe4e", 264 | "metadata": {}, 265 | "outputs": [], 266 | "source": [ 267 | "(\n", 268 | " tlt.groupby(\"year\")\n", 269 | " .last_week_less_first_week.mean()\n", 270 | " .plot.bar(title=\"Mean Log Strategy Returns by Year\")\n", 271 | ")" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": null, 277 | "id": "4a00d6d7", 278 | "metadata": {}, 279 | "outputs": [], 280 | "source": [ 281 | "(\n", 282 | " tlt.groupby(\"year\")\n", 283 | " .last_week_less_first_week.sum()\n", 284 | " .cumsum()\n", 285 | " .plot(title=\"Cumulative Sum of Returns By Year\")\n", 286 | ")" 287 | ] 288 | }, 289 | { 290 | "cell_type": "code", 291 | "execution_count": null, 292 | "id": "68cf155f", 293 | "metadata": {}, 294 | "outputs": [], 295 | "source": [ 296 | "tlt[\"last_week_less_first_week\"].cumsum().plot(\n", 297 | " title=\"Cumulative Sum of Returns By Day\"\n", 298 | ")" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": null, 304 | "id": "c5c925de", 305 | "metadata": {}, 306 | "outputs": [], 307 | "source": [ 308 | "plt.show()" 309 | ] 310 | }, 311 | { 312 | "cell_type": "markdown", 313 | "id": "a04d6e08", 314 | "metadata": {}, 315 | "source": [ 316 | "Consistent positive bars by year indicate the behavior isn’t just a couple of lucky months. Yearly cumulative sums highlight which regimes drive or fade the edge, and the daily cumulative path reveals drawdowns you would have felt. We still ignore trading costs and ETF tracking error here; if the shape looks good, add slippage and calendars next." 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "id": "735e7b39", 322 | "metadata": {}, 323 | "source": [ 324 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 325 | ] 326 | } 327 | ], 328 | "metadata": { 329 | "jupytext": { 330 | "cell_metadata_filter": "-all", 331 | "main_language": "python", 332 | "notebook_metadata_filter": "-all" 333 | } 334 | }, 335 | "nbformat": 4, 336 | "nbformat_minor": 5 337 | } 338 | -------------------------------------------------------------------------------- /a_simple_stat_arb_strategy_you_can_trade_now.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "44282607", 6 | "metadata": {}, 7 | "source": [ 8 | "
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "8ca26c13", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation\n", 17 | "Install the libraries we use so the notebook runs anywhere without manual setup." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "id": "909a1c11", 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "!pip install yfinance pandas numpy matplotlib seaborn statsmodels" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "id": "c88e4eb9", 33 | "metadata": {}, 34 | "source": [ 35 | "These are standard pip packages that cover data access (yfinance), manipulation (pandas, numpy), testing (statsmodels), and plotting (matplotlib, seaborn). If you use conda, you can install the same names via conda-forge to avoid binary build issues on some systems." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "id": "5128b83a", 41 | "metadata": {}, 42 | "source": [ 43 | "## Imports and setup\n", 44 | "We use matplotlib and seaborn for plotting, numpy and pandas for array/DataFrame work, yfinance to fetch historical prices, and statsmodels’ coint for the Engle–Granger cointegration test." 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "id": "530046a4", 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "import matplotlib.pyplot as plt\n", 55 | "import numpy as np\n", 56 | "import pandas as pd\n", 57 | "import seaborn as sns\n", 58 | "import yfinance as yf\n", 59 | "from statsmodels.tsa.stattools import coint" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "b2af95ae", 65 | "metadata": {}, 66 | "source": [ 67 | "These imports set up a minimal stack that mirrors how professionals prototype stat-arb: get data, test the relationship, and visualize the spread. Keeping the toolset small helps us focus on the mechanics (stationarity and residuals) rather than juggling libraries." 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "id": "af07d50c", 73 | "metadata": {}, 74 | "source": [ 75 | "## Define universe and download data\n", 76 | "Define a related renewable-energy universe and pull daily closes, then align the panel by dropping dates with missing values." 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "id": "f1687414", 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "tickers = [\n", 87 | " \"NEE\",\n", 88 | " \"FSLR\",\n", 89 | " \"ENPH\",\n", 90 | " \"PLUG\",\n", 91 | " \"BEP\",\n", 92 | " \"AQN\",\n", 93 | " \"PBW\",\n", 94 | " \"FAN\",\n", 95 | " \"ICLN\",\n", 96 | "]\n", 97 | "start_date = \"2014-01-01\"\n", 98 | "end_date = \"2015-01-01\"\n", 99 | "df = yf.download(\n", 100 | " tickers,\n", 101 | " start=start_date,\n", 102 | " end=end_date,\n", 103 | " auto_adjust=False,\n", 104 | ").Close\n", 105 | "df = df.dropna()" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "id": "e68406a2", 111 | "metadata": {}, 112 | "source": [ 113 | "Starting with related names increases our odds of finding economic links that support a stable residual, rather than spurious correlation. Dropping missing dates ensures the Engle–Granger test compares like-for-like observations, which protects your inference. The short window is for illustration; in practice, use longer histories and time splits to reduce sampling noise." 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "id": "cad45526", 119 | "metadata": {}, 120 | "source": [ 121 | "## Test cointegration and select pairs\n", 122 | "Run Engle–Granger tests across all pairs to score stability (p-values) and keep candidates below a permissive threshold." 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "id": "4d7d2f86", 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "n = len(tickers)\n", 133 | "score_matrix = np.zeros((n, n))\n", 134 | "pvalue_matrix = np.ones((n, n))\n", 135 | "pairs = []\n", 136 | "for i in range(n):\n", 137 | " for j in range(i + 1, n):\n", 138 | " score, pval, _ = coint(df.iloc[:, i], df.iloc[:, j])\n", 139 | " score_matrix[i, j] = score\n", 140 | " pvalue_matrix[i, j] = pval\n", 141 | " if pval < 0.10:\n", 142 | " pairs.append((tickers[i], tickers[j]))" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "id": "1e9e1748", 148 | "metadata": {}, 149 | "source": [ 150 | "Cointegration targets a stationary linear combination, which is what makes residual trading coherent when volatility shifts. A 10% screen is deliberately loose so we don’t overfit at this stage; we’ll rely on later standardization and validation to refine signals. In production research, account for multiple testing and re-estimate over rolling windows to keep parameters current." 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "id": "f7dfe0d3", 156 | "metadata": {}, 157 | "source": [ 158 | "Identify the strongest candidate (lowest p-value) and extract its two aligned price series for further analysis." 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "id": "c4b1274a", 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "mask = np.triu(np.ones_like(pvalue_matrix, dtype=bool), k=1)\n", 169 | "upper_vals = pvalue_matrix[mask]\n", 170 | "min_idx_flat = np.argmin(upper_vals)\n", 171 | "min_p = upper_vals[min_idx_flat]\n", 172 | "idx_pairs = np.column_stack(np.where(mask))\n", 173 | "i, j = idx_pairs[min_idx_flat]" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": null, 179 | "id": "8518ba7d", 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "S1, S2 = df.iloc[:, i], df.iloc[:, j]" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "id": "5c3555e3", 189 | "metadata": {}, 190 | "source": [ 191 | "Picking the best-ranked pair gives us a concrete example to carry through the workflow. Remember that the test is symmetric, but the tradable spread is not—next we should estimate a hedge ratio so the residual reflects the true linear relation rather than raw price levels. Treat this selection as a starting point to be confirmed out-of-sample." 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "id": "e6e585a9", 197 | "metadata": {}, 198 | "source": [ 199 | "## Standardize residuals and visualize signals\n", 200 | "Reconfirm the cointegration result on the chosen pair, then build a spread and a rolling z-score to make signals comparable over time." 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "id": "3d303266", 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "score, pvalue, _ = coint(S1, S2)\n", 211 | "print(\n", 212 | " f\"tickers with lowest p-value: {tickers[i]} x {tickers[j]}, p={pvalue}\"\n", 213 | ")\n", 214 | "spread = S1 - S2\n", 215 | "zscore = (\n", 216 | " spread - spread.rolling(21, min_periods=21).mean()\n", 217 | ") / spread.rolling(21, min_periods=21).std()" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "id": "dcba250b", 223 | "metadata": {}, 224 | "source": [ 225 | "Standardizing with a 21-day rolling window avoids lookahead and puts the residual in “number of sigmas,” which makes entry and exit rules clean. The spread here is a simple difference; in practice, regress S1 on S2 and use residual = S1 − beta*S2 to respect scaling. Z-scores are portable across pairs and regimes, which helps when you later compare signals or batch-trade a basket." 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "id": "03c5308e", 231 | "metadata": {}, 232 | "source": [ 233 | "Visualize the cointegration p-values, masking non-candidates and the redundant lower triangle to see which names truly co-move in a stable way." 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "id": "22e23505", 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "mask = (\n", 244 | " np.tril(np.ones_like(pvalue_matrix, dtype=bool))\n", 245 | " | (pvalue_matrix >= 0.10)\n", 246 | ")\n", 247 | "plt.figure(figsize=(8, 6))\n", 248 | "sns.heatmap(\n", 249 | " pvalue_matrix,\n", 250 | " mask=mask,\n", 251 | " xticklabels=tickers,\n", 252 | " yticklabels=tickers,\n", 253 | " cmap=\"RdYlGn_r\",\n", 254 | " annot=True,\n", 255 | " fmt=\".2f\",\n", 256 | " cbar=True,\n", 257 | " vmin=0,\n", 258 | " vmax=1,\n", 259 | ")\n", 260 | "plt.title(\"Cointegration Test p-value Matrix\")\n", 261 | "plt.show()" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "id": "b00284a2", 267 | "metadata": {}, 268 | "source": [ 269 | "A heatmap helps us spot clusters of stable relationships within the renewable theme, which is more actionable than eyeballing charts. This is how we define a tradable subset before doing any signal tuning. Keep in mind that what looks good in one year may drift, so periodic re-tests are part of a robust workflow." 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "id": "5a4970e1", 275 | "metadata": {}, 276 | "source": [ 277 | "Plot the raw spread with simple bands and the standardized z-score so we can sketch an entry/exit plan around mean reversion." 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "id": "a1a5dbb5", 284 | "metadata": {}, 285 | "outputs": [], 286 | "source": [ 287 | "fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)\n", 288 | "axes[0].plot(spread.index, spread, label=\"Spread\")\n", 289 | "axes[0].axhline(spread.mean(), color=\"black\", linestyle=\"--\", lw=1)\n", 290 | "axes[0].axhline(\n", 291 | " spread.mean() + spread.std(),\n", 292 | " color=\"red\",\n", 293 | " linestyle=\"--\",\n", 294 | " lw=1,\n", 295 | ")\n", 296 | "axes[0].axhline(\n", 297 | " spread.mean() - spread.std(),\n", 298 | " color=\"green\",\n", 299 | " linestyle=\"--\",\n", 300 | " lw=1,\n", 301 | ")\n", 302 | "axes[0].set_ylabel(\"Spread\")\n", 303 | "axes[0].legend()\n", 304 | "axes[1].plot(zscore.index, zscore, label=\"Z-score\")\n", 305 | "axes[1].axhline(0, color=\"black\", linestyle=\"--\", lw=1)\n", 306 | "axes[1].axhline(1, color=\"red\", linestyle=\"--\", lw=1)\n", 307 | "axes[1].axhline(-1, color=\"green\", linestyle=\"--\", lw=1)\n", 308 | "axes[1].set_ylabel(\"Z-score\")\n", 309 | "axes[1].legend()\n", 310 | "plt.xlabel(\"Date\")\n", 311 | "plt.suptitle(\"Spread and Rolling Z-score between ABGB and FSLR\")\n", 312 | "plt.tight_layout()\n", 313 | "plt.show()" 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "id": "07f264a9", 319 | "metadata": {}, 320 | "source": [ 321 | "Z-score bands make the rules explicit: for example, consider entries beyond ±1 and exits near 0, then evaluate with costs and borrow constraints. Treat these pictures as diagnostics; the next step is to backtest with rolling hedge-ratio estimation so bands reflect the true residual. Static thresholds are a useful baseline, but the edge lives in disciplined estimation and risk controls." 322 | ] 323 | }, 324 | { 325 | "cell_type": "markdown", 326 | "id": "f48eb190", 327 | "metadata": {}, 328 | "source": [ 329 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 330 | ] 331 | } 332 | ], 333 | "metadata": { 334 | "jupytext": { 335 | "cell_metadata_filter": "-all", 336 | "main_language": "python", 337 | "notebook_metadata_filter": "-all" 338 | } 339 | }, 340 | "nbformat": 4, 341 | "nbformat_minor": 5 342 | } 343 | -------------------------------------------------------------------------------- /how_to_compute_volatility_6_ways_most_people_don’t_know.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6e13693f", 6 | "metadata": {}, 7 | "source": [ 8 | "
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "0a83b5ca", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "c76831c7", 22 | "metadata": {}, 23 | "source": [ 24 | "Install the runtime dependencies so this notebook runs anywhere without manual setup. We include yfinance, pandas, numpy, and matplotlib for data, arrays, and plots." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "7c60b3cd", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "!pip install yfinance pandas numpy matplotlib" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "713da31a", 40 | "metadata": {}, 41 | "source": [ 42 | "We install pandas even if we do not import it directly because yfinance returns pandas DataFrames and Series. A single cell like this removes environment friction and makes the notebook portable. No special system packages are typically required for these libraries." 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "aa3218b8", 48 | "metadata": {}, 49 | "source": [ 50 | "## Imports and setup" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "id": "bd3c51b1", 56 | "metadata": {}, 57 | "source": [ 58 | "We import math for constants and square roots, numpy for fast elementwise log operations, yfinance to fetch OHLCV bars, and matplotlib.pyplot to plot the close and our volatility estimates." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "id": "54637b7b", 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "import math" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "id": "b8369240", 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "import numpy as np\n", 79 | "import yfinance as yf\n", 80 | "import matplotlib.pyplot as plt" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "id": "129d715f", 86 | "metadata": {}, 87 | "source": [ 88 | "Keeping imports minimal helps focus on measurement rather than tooling. yfinance returns pandas objects, so we can use rolling windows without importing pandas explicitly. These are the only modules we need to build, compare, and visualize the estimators." 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "id": "f0a2b7ad", 94 | "metadata": {}, 95 | "source": [ 96 | "## Download OHLCV data for testing" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "id": "931aff52", 102 | "metadata": {}, 103 | "source": [ 104 | "Download daily OHLCV for AAPL over a multi-year span to feed each estimator with realistic bar information. This includes Open, High, Low, Close that capture intraday ranges and overnight gaps we care about." 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "id": "9c3198e4", 111 | "metadata": { 112 | "lines_to_next_cell": 1 113 | }, 114 | "outputs": [], 115 | "source": [ 116 | "data = yf.download(\"AAPL\", start=\"2017-01-01\", end=\"2022-06-30\")" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "id": "2f8e82ab", 122 | "metadata": {}, 123 | "source": [ 124 | "Bar data avoids the close-only trap by retaining the range inside each session and the discontinuity between sessions. A longer sample improves stability of rolling statistics, but we will still see small-sample effects in short windows. You can swap the ticker or dates later to pressure-test how robust your sizing metric is across regimes." 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "id": "4dbcb5bc", 130 | "metadata": {}, 131 | "source": [ 132 | "## Define realized volatility estimators clearly" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "id": "7f2b85af", 138 | "metadata": {}, 139 | "source": [ 140 | "Implement six realized volatility estimators on OHLC bars, all annualized to trading_periods and computed with rolling windows that avoid lookahead by using only past data. Each function returns a Series aligned with the input index and can drop warm-up NaNs via clean=True." 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "id": "c69aa31c", 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "def standard_deviation(price_data, window=30, trading_periods=252, clean=True):\n", 151 | " log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n", 152 | "\n", 153 | " result = (\n", 154 | " log_return.rolling(window=window, center=False).std()\n", 155 | " * math.sqrt(trading_periods)\n", 156 | " )\n", 157 | "\n", 158 | " if clean:\n", 159 | " return result.dropna()\n", 160 | " else:\n", 161 | " return result" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "id": "1da8695f", 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "def parkinson(price_data, window=30, trading_periods=252, clean=True):\n", 172 | " rs = (1.0 / (4.0 * math.log(2.0))) * (\n", 173 | " (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n", 174 | " ) ** 2.0\n", 175 | "\n", 176 | " def f(v):\n", 177 | " return (trading_periods * v.mean()) ** 0.5\n", 178 | "\n", 179 | " result = rs.rolling(window=window, center=False).apply(func=f)\n", 180 | "\n", 181 | " if clean:\n", 182 | " return result.dropna()\n", 183 | " else:\n", 184 | " return result" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "id": "84ba5f9f", 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "def garman_klass(price_data, window=30, trading_periods=252, clean=True):\n", 195 | " log_hl = (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n", 196 | " log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n", 197 | "\n", 198 | " rs = 0.5 * log_hl ** 2 - (2 * math.log(2) - 1) * log_co ** 2\n", 199 | "\n", 200 | " def f(v):\n", 201 | " return (trading_periods * v.mean()) ** 0.5\n", 202 | "\n", 203 | " result = rs.rolling(window=window, center=False).apply(func=f)\n", 204 | "\n", 205 | " if clean:\n", 206 | " return result.dropna()\n", 207 | " else:\n", 208 | " return result" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "id": "ad975476", 215 | "metadata": {}, 216 | "outputs": [], 217 | "source": [ 218 | "def hodges_tompkins(price_data, window=30, trading_periods=252, clean=True):\n", 219 | " log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n", 220 | "\n", 221 | " vol = (\n", 222 | " log_return.rolling(window=window, center=False).std()\n", 223 | " * math.sqrt(trading_periods)\n", 224 | " )\n", 225 | "\n", 226 | " h = window\n", 227 | " n = (log_return.count() - h) + 1\n", 228 | "\n", 229 | " adj_factor = 1.0 / (\n", 230 | " 1.0 - (h / n) + ((h ** 2 - 1) / (3 * (n ** 2)))\n", 231 | " )\n", 232 | "\n", 233 | " result = vol * adj_factor\n", 234 | "\n", 235 | " if clean:\n", 236 | " return result.dropna()\n", 237 | " else:\n", 238 | " return result" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": null, 244 | "id": "0f984959", 245 | "metadata": {}, 246 | "outputs": [], 247 | "source": [ 248 | "def rogers_satchell(price_data, window=30, trading_periods=252, clean=True):\n", 249 | " log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n", 250 | " log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n", 251 | " log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n", 252 | "\n", 253 | " rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n", 254 | "\n", 255 | " def f(v):\n", 256 | " return (trading_periods * v.mean()) ** 0.5\n", 257 | "\n", 258 | " result = rs.rolling(window=window, center=False).apply(func=f)\n", 259 | "\n", 260 | " if clean:\n", 261 | " return result.dropna()\n", 262 | " else:\n", 263 | " return result" 264 | ] 265 | }, 266 | { 267 | "cell_type": "code", 268 | "execution_count": null, 269 | "id": "03add9a4", 270 | "metadata": { 271 | "lines_to_next_cell": 1 272 | }, 273 | "outputs": [], 274 | "source": [ 275 | "def yang_zhang(price_data, window=30, trading_periods=252, clean=True):\n", 276 | " log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n", 277 | " log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n", 278 | " log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n", 279 | "\n", 280 | " log_oc = (price_data[\"Open\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n", 281 | " log_oc_sq = log_oc ** 2\n", 282 | "\n", 283 | " log_cc = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n", 284 | " log_cc_sq = log_cc ** 2\n", 285 | "\n", 286 | " rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n", 287 | "\n", 288 | " close_vol = (\n", 289 | " log_cc_sq.rolling(window=window, center=False).sum()\n", 290 | " * (1.0 / (window - 1.0))\n", 291 | " )\n", 292 | " open_vol = (\n", 293 | " log_oc_sq.rolling(window=window, center=False).sum()\n", 294 | " * (1.0 / (window - 1.0))\n", 295 | " )\n", 296 | " window_rs = (\n", 297 | " rs.rolling(window=window, center=False).sum()\n", 298 | " * (1.0 / (window - 1.0))\n", 299 | " )\n", 300 | "\n", 301 | " k = 0.34 / (1.34 + (window + 1) / (window - 1))\n", 302 | " result = (\n", 303 | " (open_vol + k * close_vol + (1 - k) * window_rs).apply(np.sqrt)\n", 304 | " * math.sqrt(trading_periods)\n", 305 | " )\n", 306 | "\n", 307 | " if clean:\n", 308 | " return result.dropna()\n", 309 | " else:\n", 310 | " return result" 311 | ] 312 | }, 313 | { 314 | "cell_type": "markdown", 315 | "id": "b816e969", 316 | "metadata": {}, 317 | "source": [ 318 | "standard_deviation is the close-to-close baseline; hodges_tompkins debiases its small-sample downward bias from overlapping windows. parkinson and garman_klass use high–low (and open–close) ranges for higher efficiency; rogers_satchell is drift-robust, and yang_zhang blends overnight gap and intraday range to handle open–close discontinuities. These options let us see how bar-based measures react to spikes and gaps that closes miss, then pick a stable sizing input." 319 | ] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "id": "ff3b9faf", 324 | "metadata": {}, 325 | "source": [ 326 | "## Visualize and compare volatility estimates" 327 | ] 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "id": "12b5e0ec", 332 | "metadata": {}, 333 | "source": [ 334 | "Plot the close and all six estimators together to inspect when they agree and when they diverge around gaps, trend days, and stress. We care about relative timing and spikes more than exact levels on this axis." 335 | ] 336 | }, 337 | { 338 | "cell_type": "code", 339 | "execution_count": null, 340 | "id": "80a22b2b", 341 | "metadata": {}, 342 | "outputs": [], 343 | "source": [ 344 | "data[\"Close\"].plot()\n", 345 | "standard_deviation(data).plot()\n", 346 | "parkinson(data).plot()\n", 347 | "garman_klass(data).plot()\n", 348 | "hodges_tompkins(data).plot()\n", 349 | "rogers_satchell(data).plot()\n", 350 | "yang_zhang(data).plot()\n", 351 | "plt.show()" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "id": "674b9d94", 357 | "metadata": {}, 358 | "source": [ 359 | "The curves will not share units with price, so focus on how each estimator jumps and mean-reverts when regimes shift. In practice we would validate a candidate metric by comparing it to realized next-day moves or high–low ranges and then size positions off the most stable forecast. Watch for cases where close-vol looks calm while range-based measures surge; that mismatch is the failure mode we want to eliminate." 360 | ] 361 | }, 362 | { 363 | "cell_type": "markdown", 364 | "id": "1f208f8b", 365 | "metadata": {}, 366 | "source": [ 367 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 368 | ] 369 | } 370 | ], 371 | "metadata": { 372 | "jupytext": { 373 | "cell_metadata_filter": "-all", 374 | "main_language": "python", 375 | "notebook_metadata_filter": "-all" 376 | } 377 | }, 378 | "nbformat": 4, 379 | "nbformat_minor": 5 380 | } 381 | -------------------------------------------------------------------------------- /how_to_simulate_stock_prices_with_python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "7f8b3f16", 6 | "metadata": {}, 7 | "source": [ 8 | "
PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk.
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "616a6ec6", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "b5f1d0c5", 22 | "metadata": {}, 23 | "source": [ 24 | "Install numpy and matplotlib so the notebook can simulate and plot GBM paths. This ensures a clean, reproducible environment regardless of your local setup." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "51fab65b", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "!pip install numpy matplotlib" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "6bbacdc5", 40 | "metadata": {}, 41 | "source": [ 42 | "If you are running in an environment that already ships these libraries (such as Colab or some IDEs), this command will simply confirm versions. Keeping dependencies minimal lets us focus on modeling choices like time-step units and volatility scaling rather than environment issues." 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "33bfb5d0", 48 | "metadata": {}, 49 | "source": [ 50 | "## Imports and setup" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "id": "2529b9a9", 56 | "metadata": {}, 57 | "source": [ 58 | "We use numpy for fast vectorized arrays and random draws, and matplotlib.pyplot for quick visual checks of simulated price paths." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "id": "37edd0c7", 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "import numpy as np\n", 69 | "import matplotlib.pyplot as plt" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "id": "54cf882a", 75 | "metadata": {}, 76 | "source": [ 77 | "Visual checks are a core part of simulation work because they surface unit mismatches early. If you want reproducible runs later, you can set a seed with numpy’s RNG before calling any functions; reproducibility is essential when diagnosing differences across parameter choices." 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "id": "3a6efa61", 83 | "metadata": {}, 84 | "source": [ 85 | "## Define simulation parameters and units" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "a0598b63", 91 | "metadata": {}, 92 | "source": [ 93 | "Set the initial price, annualized volatility, and annualized drift, then define the Monte Carlo grid (paths, daily time step, total horizon). Using delta = 1/252 converts annual inputs into daily increments, and time = 252*5 targets a five-year window." 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "id": "0eff7483", 100 | "metadata": { 101 | "lines_to_next_cell": 1 102 | }, 103 | "outputs": [], 104 | "source": [ 105 | "s0 = 131.00\n", 106 | "sigma = 0.25\n", 107 | "mu = 0.35\n", 108 | "paths = 1000\n", 109 | "delta = 1.0 / 252.0\n", 110 | "time = 252 * 5" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "id": "28cf4ef0", 116 | "metadata": {}, 117 | "source": [ 118 | "Locking units up front prevents the classic mistake of feeding annual parameters into daily steps without the proper scaling. Consistent definitions make results interpretable and comparable as you vary drift or volatility. paths controls Monte Carlo precision, so increasing it is how we check convergence of any statistic we care about." 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "id": "1e2a69e4", 124 | "metadata": {}, 125 | "source": [ 126 | "## Build GBM and helper functions" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "id": "17268d20", 132 | "metadata": {}, 133 | "source": [ 134 | "Generate Brownian shocks whose variance matches the chosen time step and whose scale reflects the (annualized) volatility. Each column is an independent price path, and rows step forward in time." 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "id": "a64a8fe8", 141 | "metadata": { 142 | "lines_to_next_cell": 1 143 | }, 144 | "outputs": [], 145 | "source": [ 146 | "def wiener_process(delta, sigma, time, paths):\n", 147 | " \"\"\"Returns a Wiener process.\n", 148 | "\n", 149 | " Parameters\n", 150 | " ----------\n", 151 | " delta : float\n", 152 | " The increment to downsample sigma.\n", 153 | " sigma : float\n", 154 | " Percentage volatility.\n", 155 | " time : int\n", 156 | " Number of samples to create.\n", 157 | " paths : int\n", 158 | " Number of price simulations to create.\n", 159 | "\n", 160 | " Returns\n", 161 | " -------\n", 162 | " wiener_process : np.ndarray\n", 163 | "\n", 164 | " Notes\n", 165 | " -----\n", 166 | " This method returns a Wiener process. The Wiener process is also called\n", 167 | " Brownian motion. For more information about the Wiener process check out\n", 168 | " the Wikipedia page:\n", 169 | " http://en.wikipedia.org/wiki/Wiener_process\n", 170 | " \"\"\"\n", 171 | " return sigma * np.random.normal(\n", 172 | " loc=0,\n", 173 | " scale=np.sqrt(delta),\n", 174 | " size=(time, paths),\n", 175 | " )" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "id": "dffd23fa", 181 | "metadata": {}, 182 | "source": [ 183 | "Scaling by sqrt(delta) gives the correct time-step variance, and multiplying by sigma downscales the annual volatility to the per-step level. This independence-by-path assumption is the default baseline in pricing engines and is ideal for sandbox learning. Vectorized generation keeps the simulation fast enough to run thousands of paths interactively." 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "id": "0dd3567d", 189 | "metadata": {}, 190 | "source": [ 191 | "Convert Brownian shocks into multiplicative GBM returns using the closed-form step of a constant-drift, constant-vol process. The (mu - 0.5*sigma**2)*delta term applies the Itô correction so expected log returns line up with the specified drift." 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": null, 197 | "id": "60616bb8", 198 | "metadata": { 199 | "lines_to_next_cell": 1 200 | }, 201 | "outputs": [], 202 | "source": [ 203 | "def gbm_returns(delta, sigma, time, mu, paths):\n", 204 | " \"\"\"Returns from a Geometric Brownian Motion (GBM).\n", 205 | "\n", 206 | " Parameters\n", 207 | " ----------\n", 208 | " delta : float\n", 209 | " The increment to downsample sigma.\n", 210 | " sigma : float\n", 211 | " Percentage volatility.\n", 212 | " time : int\n", 213 | " Number of samples to create.\n", 214 | " mu : float\n", 215 | " Percentage drift.\n", 216 | " paths : int\n", 217 | " Number of price simulations to create.\n", 218 | "\n", 219 | " Returns\n", 220 | " -------\n", 221 | " gbm_returns : np.ndarray\n", 222 | "\n", 223 | " Notes\n", 224 | " -----\n", 225 | " This method constructs random Geometric Brownian Motion (GBM).\n", 226 | " \"\"\"\n", 227 | " process = wiener_process(delta, sigma, time, paths)\n", 228 | " return np.exp(process + (mu - sigma**2 / 2) * delta)" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "id": "259d0647", 234 | "metadata": {}, 235 | "source": [ 236 | "Many beginners omit the 0.5*sigma^2 term and end up with biased levels that drift too high. Keeping drift and volatility annualized while applying delta per step is the key to unit consistency. This block encodes those rules in one place so we can change parameters confidently." 237 | ] 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "id": "b10c5f55", 242 | "metadata": {}, 243 | "source": [ 244 | "Compound the simulated returns from the initial price to produce level paths while ensuring strictly positive prices. We prepend ones so the first row represents the starting point before any move." 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": null, 250 | "id": "ea7a7279", 251 | "metadata": { 252 | "lines_to_next_cell": 1 253 | }, 254 | "outputs": [], 255 | "source": [ 256 | "def gbm_levels(s0, delta, sigma, time, mu, paths):\n", 257 | " \"\"\"Returns price paths starting at s0.\n", 258 | "\n", 259 | " Parameters\n", 260 | " ----------\n", 261 | " s0 : float\n", 262 | " The starting stock price.\n", 263 | " delta : float\n", 264 | " The increment to downsample sigma.\n", 265 | " sigma : float\n", 266 | " Percentage volatility.\n", 267 | " time : int\n", 268 | " Number of samples to create.\n", 269 | " mu : float\n", 270 | " Percentage drift.\n", 271 | " paths : int\n", 272 | " Number of price simulations to create.\n", 273 | "\n", 274 | " Returns\n", 275 | " -------\n", 276 | " gbm_levels : np.ndarray\n", 277 | " \"\"\"\n", 278 | " returns = gbm_returns(delta, sigma, time, mu, paths)\n", 279 | " stacked = np.vstack([np.ones(paths), returns])\n", 280 | " return s0 * stacked.cumprod(axis=0)" 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "id": "280c57fd", 286 | "metadata": {}, 287 | "source": [ 288 | "Working with levels is how option desks and risk teams visualize scenarios and payoffs. GBM’s multiplicative compounding matches how real prices behave and avoids negative levels that plague additive models. Including the initial price makes plots and summary stats easier to interpret." 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "id": "fa785bd6", 294 | "metadata": {}, 295 | "source": [ 296 | "## Run scenarios and visualize paths" 297 | ] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "id": "88ee0f93", 302 | "metadata": {}, 303 | "source": [ 304 | "Simulate paths with a positive drift and compute how many finishes end above the starting price as a quick sanity check. In notebooks this last expression will display and gives a rough sense of the distribution’s tilt under positive drift." 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": null, 310 | "id": "035fa3f7", 311 | "metadata": {}, 312 | "outputs": [], 313 | "source": [ 314 | "price_paths = gbm_levels(s0, delta, sigma, time, mu, paths)\n", 315 | "len(price_paths[-1, price_paths[-1, :] > s0])" 316 | ] 317 | }, 318 | { 319 | "cell_type": "markdown", 320 | "id": "b2caad37", 321 | "metadata": {}, 322 | "source": [ 323 | "This quick diagnostic connects drift to long-run tendency without overfitting historical quirks. As you scale paths higher, the fraction should stabilize, which is a simple convergence check. It’s a habit pros use before trusting any simulated payoff estimates." 324 | ] 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "id": "3735f237", 329 | "metadata": {}, 330 | "source": [ 331 | "Plot the simulated paths to visually verify unit choices and compounding behavior. Thin lines help us inspect many paths at once." 332 | ] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": null, 337 | "id": "c5c5ff0d", 338 | "metadata": {}, 339 | "outputs": [], 340 | "source": [ 341 | "plt.plot(price_paths, linewidth=0.25)\n", 342 | "plt.show()" 343 | ] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "id": "5473ae6d", 348 | "metadata": {}, 349 | "source": [ 350 | "Early plots catch mistakes like mismatched time steps or mis-scaled volatility before you burn time on strategy code. Visual checks pair with seeded randomness to make discrepancies obvious when you tweak parameters. Treat these figures as fast feedback loops, not final analyses." 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "id": "c0500dc0", 356 | "metadata": {}, 357 | "source": [ 358 | "Rerun the engine with zero drift to isolate volatility’s effect and compare to the positive-drift case. Visualizing both back-to-back reinforces how drift sets direction while volatility controls dispersion." 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": null, 364 | "id": "ce37b9f0", 365 | "metadata": {}, 366 | "outputs": [], 367 | "source": [ 368 | "price_paths = gbm_levels(s0, delta, sigma, time, 0.0, paths)\n", 369 | "plt.plot(price_paths, linewidth=0.25)\n", 370 | "plt.show()" 371 | ] 372 | }, 373 | { 374 | "cell_type": "markdown", 375 | "id": "73180f90", 376 | "metadata": {}, 377 | "source": [ 378 | "A zero-drift scenario is a common baseline in pricing and risk because it mirrors a risk-neutral setting for intuition-building. You should see no systematic trend in log space, yet level paths remain positively skewed due to compounding. Use this contrast to validate that your step size and scaling behave as expected." 379 | ] 380 | }, 381 | { 382 | "cell_type": "markdown", 383 | "id": "6355b4a7", 384 | "metadata": {}, 385 | "source": [ 386 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 387 | ] 388 | }, 389 | { 390 | "cell_type": "code", 391 | "execution_count": null, 392 | "id": "f4831cb7-5a54-442f-a08d-2ede52162306", 393 | "metadata": {}, 394 | "outputs": [], 395 | "source": [] 396 | } 397 | ], 398 | "metadata": { 399 | "jupytext": { 400 | "cell_metadata_filter": "-all", 401 | "main_language": "python", 402 | "notebook_metadata_filter": "-all" 403 | }, 404 | "kernelspec": { 405 | "display_name": "Python 3 (ipykernel)", 406 | "language": "python", 407 | "name": "python3" 408 | }, 409 | "language_info": { 410 | "codemirror_mode": { 411 | "name": "ipython", 412 | "version": 3 413 | }, 414 | "file_extension": ".py", 415 | "mimetype": "text/x-python", 416 | "name": "python", 417 | "nbconvert_exporter": "python", 418 | "pygments_lexer": "ipython3", 419 | "version": "3.12.9" 420 | } 421 | }, 422 | "nbformat": 4, 423 | "nbformat_minor": 5 424 | } 425 | -------------------------------------------------------------------------------- /value_at_risk_manage_your_portfolios_risk.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6771ed6e", 6 | "metadata": {}, 7 | "source": [ 8 | "
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "0898293c", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation\n", 17 | "Install the required Python packages so this notebook can fetch data, compute statistics, and plot results anywhere you run it. This ensures a clean, reproducible setup for the VaR workflow." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "id": "e679c8aa", 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "!pip install yfinance pandas numpy matplotlib scipy" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "id": "819fbfb4", 33 | "metadata": {}, 34 | "source": [ 35 | "yfinance pulls prices, pandas and numpy handle the tabular math, scipy provides the normal quantile, and matplotlib draws the chart. Running this cell once per environment avoids import errors later. If you are in a locked environment, confirm these are preinstalled and skip the install." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "id": "58037a61", 41 | "metadata": {}, 42 | "source": [ 43 | "## Imports and setup\n", 44 | "We import pandas for DataFrame operations, numpy for vectorized linear algebra, scipy.stats.norm for the normal quantile, matplotlib.pyplot for plotting, and yfinance to download prices." 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 1, 50 | "id": "f6ea1df0", 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "import pandas as pd\n", 55 | "import numpy as np\n", 56 | "from scipy.stats import norm\n", 57 | "import matplotlib.pyplot as plt\n", 58 | "import yfinance as yf" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "id": "aebdea5c", 64 | "metadata": {}, 65 | "source": [ 66 | "This small stack is enough to replicate a desk-style daily VaR check without heavy infrastructure. Keeping versions stable helps avoid small numerical drift across runs. In production, it’s good practice to pin versions and log them alongside your daily risk output." 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "id": "1ff69b72", 72 | "metadata": {}, 73 | "source": [ 74 | "## Define portfolio and fetch prices\n", 75 | "Set the portfolio composition, capital base, and tail probability, then download daily closes with yfinance over a fixed window. This establishes what risk we measure and provides the price history needed for returns." 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "id": "1b72b58f", 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "tickers = [\"AAPL\", \"META\", \"C\", \"DIS\"]\n", 86 | "weights = np.array([0.25, 0.3, 0.15, 0.3])\n", 87 | "portfolio_value = 1_000\n", 88 | "confidence = 0.05" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 5, 94 | "id": "7e06b999", 95 | "metadata": {}, 96 | "outputs": [ 97 | { 98 | "name": "stderr", 99 | "output_type": "stream", 100 | "text": [ 101 | "/var/folders/6m/0ykpdmvn3lb5qkq15hk3s11c0000gn/T/ipykernel_22291/2418303877.py:1: FutureWarning: YF.download() has changed argument auto_adjust default to True\n", 102 | " data = yf.download(\n", 103 | "[*********************100%***********************] 4 of 4 completed\n" 104 | ] 105 | } 106 | ], 107 | "source": [ 108 | "data = yf.download(\n", 109 | " tickers, start=\"2018-01-01\", end=\"2021-12-31\"\n", 110 | ")[\"Close\"]" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "id": "7334e700", 116 | "metadata": {}, 117 | "source": [ 118 | "Weights should sum to one, capital sets the dollar scale, and alpha=0.05 targets the left 5% tail for a 95% one-sided check. Always confirm symbols and data quality; FB is now META and adjusted closes are preferable when accounting for splits and dividends. In a daily workflow, pull a recent window and rerun this block each morning before sizing positions." 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "id": "0b283f80", 124 | "metadata": {}, 125 | "source": [ 126 | "## Compute returns and estimate moments\n", 127 | "Convert prices to daily percentage returns, estimate each asset’s mean and the covariance matrix, and combine them with weights to get portfolio drift and volatility. Express both in fraction and dollar terms to prepare for a dollar VaR." 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 6, 133 | "id": "dc249469", 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "returns = data.pct_change()\n", 138 | "mean_returns = returns.mean()\n", 139 | "port_mean = mean_returns.dot(weights)\n", 140 | "investment_mean = (1 + port_mean) * portfolio_value\n", 141 | "cov_matrix = returns.cov()\n", 142 | "port_stdev = np.sqrt(weights.T.dot(cov_matrix).dot(weights))\n", 143 | "investment_stdev = portfolio_value * port_stdev" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "id": "57c1b295", 149 | "metadata": {}, 150 | "source": [ 151 | "The covariance captures co-movement, which is the key upgrade over eyeballing single names. A recent rolling window (about 30–60 trading days for equities) keeps this estimate current; long samples can stale your risk. Drift is small day to day, so many teams ignore it for speed, but we include it so you can see the full parametric form." 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "id": "596f76b5", 157 | "metadata": {}, 158 | "source": [ 159 | "## Calculate VaR and scale horizon\n", 160 | "Map the left tail of the normal distribution to a dollar loss using norm.ppf with alpha, the mean, and the dollar volatility. Convert that threshold into a one-day VaR and extend it across multiple days under the square-root-of-time rule." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 7, 166 | "id": "9c073a48", 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "name": "stdout", 171 | "output_type": "stream", 172 | "text": [ 173 | "Portfolio VaR: 28.257793003030656\n" 174 | ] 175 | } 176 | ], 177 | "source": [ 178 | "percent_point = norm.ppf(confidence, investment_mean, investment_stdev)\n", 179 | "value_at_risk = portfolio_value - percent_point\n", 180 | "print(f\"Portfolio VaR: {value_at_risk}\")" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 8, 186 | "id": "52edba25", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "value_at_risks = value_at_risk * np.sqrt(range(1, 31))" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 9, 196 | "id": "49310654", 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "data": { 201 | "text/plain": [ 202 | "[]" 203 | ] 204 | }, 205 | "execution_count": 9, 206 | "metadata": {}, 207 | "output_type": "execute_result" 208 | }, 209 | { 210 | "data": { 211 | "image/png": "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", 212 | "text/plain": [ 213 | "
" 214 | ] 215 | }, 216 | "metadata": {}, 217 | "output_type": "display_data" 218 | } 219 | ], 220 | "source": [ 221 | "plt.xlabel(\"Day\")\n", 222 | "plt.ylabel(\"Max loss\")\n", 223 | "plt.title(\"Portfolio VaR\")\n", 224 | "plt.plot(value_at_risks, \"r\")" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "id": "8b5cfc85", 230 | "metadata": {}, 231 | "source": [ 232 | "With small drift, the 95% parametric VaR is roughly 1.645 times dollar volatility, which is a good mental check against the printed value. Scaling assumes independent daily returns and stable volatility; crises, gaps, and fat tails can break this, so also inspect 99% and add simple shocks. The curve gives a concrete loss budget you can use to cap size and leverage per strategy." 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "id": "43e5fa68", 238 | "metadata": {}, 239 | "source": [ 240 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 241 | ] 242 | } 243 | ], 244 | "metadata": { 245 | "jupytext": { 246 | "cell_metadata_filter": "-all", 247 | "main_language": "python", 248 | "notebook_metadata_filter": "-all" 249 | }, 250 | "kernelspec": { 251 | "display_name": "Python 3 (ipykernel)", 252 | "language": "python", 253 | "name": "python3" 254 | }, 255 | "language_info": { 256 | "codemirror_mode": { 257 | "name": "ipython", 258 | "version": 3 259 | }, 260 | "file_extension": ".py", 261 | "mimetype": "text/x-python", 262 | "name": "python", 263 | "nbconvert_exporter": "python", 264 | "pygments_lexer": "ipython3", 265 | "version": "3.12.9" 266 | } 267 | }, 268 | "nbformat": 4, 269 | "nbformat_minor": 5 270 | } 271 | -------------------------------------------------------------------------------- /how_to_tell_if_options_are_cheap_with_volatility_cones.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "3bccd3fa", 6 | "metadata": {}, 7 | "source": [ 8 | "
" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "da59df26", 14 | "metadata": {}, 15 | "source": [ 16 | "## Library installation\n", 17 | "Install the libraries needed to fetch market data, compute realized volatility, and plot the cone so the notebook runs end-to-end." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "id": "a70da6d5", 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "!pip install yfinance pandas numpy matplotlib" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "id": "420e9534", 33 | "metadata": {}, 34 | "source": [ 35 | "## Imports and setup\n", 36 | "We use math for annualization, yfinance to download daily prices, numpy for vectorized math on returns, and matplotlib.pyplot to visualize the volatility cone." 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 9, 42 | "id": "1a9d52eb", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "import math" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 10, 52 | "id": "0db724fd", 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "import matplotlib.pyplot as plt\n", 57 | "import numpy as np\n", 58 | "import pandas as pd\n", 59 | "import yfinance as yf" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "bf338f7f", 65 | "metadata": {}, 66 | "source": [ 67 | "This small stack mirrors what desks use for quick pre-trade checks without heavy dependencies. Keeping tools simple encourages a consistent process, which matters more than model complexity when judging whether options are cheap or rich. Annualizing with math.sqrt(252) aligns our realized metric with how implied volatility is quoted." 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "id": "1e2d7555", 73 | "metadata": {}, 74 | "source": [ 75 | "## Define cone parameters and collect data\n", 76 | "Set the lookback windows we will test and the quantile bands to summarize, and prepare containers to hold each window’s historical stats and the latest realized value." 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 11, 82 | "id": "50bc34d9", 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "windows = [30, 60, 90, 120]\n", 87 | "quantiles = [0.25, 0.75]" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 12, 93 | "id": "5ffe17d7", 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "min_ = []\n", 98 | "max_ = []\n", 99 | "median = []\n", 100 | "top_q = []\n", 101 | "bottom_q = []\n", 102 | "realized = []" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "id": "d1c8e3a2", 108 | "metadata": {}, 109 | "source": [ 110 | "Each window maps to a common option tenor, so we can compare realized behavior on the same horizon as the trade. Quantile bands give a robust sense of typical ranges instead of chasing extremes. Preallocating these lists keeps the loop clean and avoids mixing statistics across windows." 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "id": "2c0fcc78", 116 | "metadata": {}, 117 | "source": [ 118 | "Download daily JPM close prices for 2020 using yfinance; this is the raw material for realized volatility." 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 13, 124 | "id": "1c5e5c03", 125 | "metadata": { 126 | "lines_to_next_cell": 1 127 | }, 128 | "outputs": [ 129 | { 130 | "name": "stderr", 131 | "output_type": "stream", 132 | "text": [ 133 | "/var/folders/6m/0ykpdmvn3lb5qkq15hk3s11c0000gn/T/ipykernel_99966/3699040392.py:1: FutureWarning: YF.download() has changed argument auto_adjust default to True\n", 134 | " data = yf.download(\"JPM\", start=\"2020-01-01\", end=\"2025-12-31\")\n", 135 | "[*********************100%***********************] 1 of 1 completed\n" 136 | ] 137 | } 138 | ], 139 | "source": [ 140 | "data = yf.download(\"JPM\", start=\"2020-01-01\", end=\"2025-12-31\")" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "id": "946de500", 146 | "metadata": {}, 147 | "source": [ 148 | "We measure realized volatility from what the market actually did rather than guessing. Use trading-day data to stay consistent with the 252-day annualization. In practice, pull enough history to fill the longest window so the most recent estimates are not NaN." 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "id": "d58fab67", 154 | "metadata": {}, 155 | "source": [ 156 | "## Compute rolling realized volatility by window\n", 157 | "Define a helper that converts rolling log-return variability into annualized realized volatility for a chosen window." 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 14, 163 | "id": "c24c6ea6", 164 | "metadata": { 165 | "lines_to_next_cell": 1 166 | }, 167 | "outputs": [], 168 | "source": [ 169 | "def realized_vol(price_data, window=30):\n", 170 | " log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n", 171 | " return log_return.rolling(window=window, center=False).std() * math.sqrt(252)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "id": "0799387b", 177 | "metadata": {}, 178 | "source": [ 179 | "Log returns make volatility scale-stable across price levels, which is important when comparing periods. Annualizing with the square root of 252 puts realized on the same units as implied volatility. Recomputing this per window builds the cone’s tenor-specific histories." 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "id": "8e17351c", 185 | "metadata": {}, 186 | "source": [ 187 | "Evaluate the realized volatility series for each window and summarize its historical distribution into bands." 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": 15, 193 | "id": "a19b4ccd", 194 | "metadata": {}, 195 | "outputs": [], 196 | "source": [ 197 | "def _scalar(x):\n", 198 | " # robustly convert Series/ndarray/scalar -> Python float\n", 199 | " return float(np.asarray(x).squeeze())\n", 200 | "\n", 201 | "rows = []\n", 202 | "for w in windows:\n", 203 | " est = realized_vol(price_data=data, window=w).dropna()\n", 204 | "\n", 205 | " rows.append({\n", 206 | " \"window\": w,\n", 207 | " \"Min\": _scalar(est.min()),\n", 208 | " \"Max\": _scalar(est.max()),\n", 209 | " \"Median\": _scalar(est.median()),\n", 210 | " f\"{quantiles[1]*100:.0f} Prctl\": _scalar(est.quantile(quantiles[1])),\n", 211 | " f\"{quantiles[0]*100:.0f} Prctl\": _scalar(est.quantile(quantiles[0])),\n", 212 | " \"Realized\": _scalar(est.iloc[-1]),\n", 213 | " })\n", 214 | "\n", 215 | "dfw = pd.DataFrame(rows).sort_values(\"window\")" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "id": "ad3777a9", 221 | "metadata": {}, 222 | "source": [ 223 | "These summaries create a stable yardstick by horizon, which is the core idea of a volatility cone. We also record today’s realized per window to see where it sits within its own historical range. Be mindful of thin history or recent events, which influence short windows more than long ones." 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "id": "dbe26f86", 229 | "metadata": {}, 230 | "source": [ 231 | "## Visualize volatility cone results clearly\n", 232 | "Plot each window’s distribution bands and overlay the latest realized point so we can judge today against history at matching tenors." 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 16, 238 | "id": "6fbbb701", 239 | "metadata": {}, 240 | "outputs": [ 241 | { 242 | "data": { 243 | "image/png": "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AAAAEe5I7fvvvrceofr3Qw/5zD/1xBPac889tfsee8RN6DZMwP56S4ZSqtvvZbi60a6nlubqmWU5Vin5jLHFemL/9VpelqxHFufqjVXZqnPQigAAAAAAIiGm05ipKp6WnqaKigr1dCaoz1mXrePm5euEedtpRXmybpm8SZ8fs0KXji9STkpLdXUAAAAAQPeI6SHDTjjxRKWlpunTTz8Juo1p+20eLeK9yrVNPxSl64LP0jUsq17njS7WxeO2Wo//rOylx5bkanVFcqRPEgAAAAB6hJgN3fsfcIBOO+N0/fnGm1RWWhZ0u5NPPUWnn3GGZ766ukYzZlyu5IyMqB88PjE5Wcrs/P4bnRn62+Ic3b+yUb8dVqjTtt+i00aW6r+beuvxFf31Y3FGD7gJAaArrysAEAjXFiA0NluC+R/Z7DbZnDFd6bhbajXLHvnPyHxX5jtLTk+T09m68LK95sAxG7qn7D9Fl11xuW7+29/1v59+anPbl198Sa+/+prXEpvSMvNUX1UVlb2X+8gM7UtszyZJdxVn64H/ZerY7cs1fUyxXthniX4qSrU6XTOdt/l31AYgTnXRdQUAuLYAneu9PCU7Q06HU06HI6Y+wllXXamphxyid95+R/fdc4/Puosvmamjjj5a78+bpztvv6NrXtBuj4rPyGlzSk6n6qtrOt3jfORvHXTQlP331xVXXql/3HKrvvv223a3b2hoUHV1tedRU1Otnsp0qPbSit6a+s72OufjIaptsuvBfQv08VErddaoYqUnRv6PGgAAAED77Dan9uhXpaPzy61nMx9uW7ZssQpAk03NuWamKa+phbx58+awv36siviQYYMGDfLM9x8wQMOHD7c6RissLNTZ556jPn366o7bbrPWmy/zyquv0kMPPqglixcrJyfHWl5XV2cFaoTGDCX2UUGm9RifU6vpY4t1w2+26IoJRXpueY6eXJKjwtqYrAQBAAAAxD0zatGNu2zWoIyWzpILqhL15x/6W7VYw2X58uUaOHCg9t5nb3304UfWsr332UeFWwq1abOpX+uyy6676re/+63yhw2Tw+HQooWL9O8HH9TGjRut9QcdfLBmXnqJLrnoYhUUFFjLzPxOkybp0otnWvkunkS0pHvkqFG6/6EHrYdxwYUXWtNnnHWmNZ+bm6t+/fI82x92+GFKTEzUJZdequdfetHzuPDiiyL2HmLdryWpuuzLQdpv9gi9srKXzhxVoi+OWa7bdt+oUb3i648dAAAAiIfA/eC+GzQg3Xd0IjNvlpv14TRv7lxNPWSaZ/6QadOsauX+hauvvfqafn/Jpfq/a66V0+nQDTf+ydVOW9J/P/hA3337na657jrZ7XbtNnmyph16qP55661xF7gNW/7IyVHesLlrmS86Ny9fxYVror5Nd0pm++N0d7WspCadOqJU54wuse6cfVKQYbX7/mJzOp2uAXEgEtcVAPGPawsQepvurOy+qigv6lT7YFOF/POjV1gB2/Tv5c/hlDZVJ2qf2SO6vM8m06Y7IzNT99z1Lz393LOafu551vKHH3tUZ552ui6bdYWqKisDtunOzs7WS/95RRfOmKE1q9dYyzIzM/XAvx/SN19/rb323luz33hTL734omcfW5S06d7W78ygDjF8VDQk6JHFffTEklwdkV+uGWOK9dxB67SoJMUK32+vzVaDg07XAAAAgK6SmuDQiOz6drebkFvjU6XcnwniZv3Jw0s1vzit3eOtKE+2+nnqiLKyMn37zbc6+JCpVoGm6WervLzcZxvThNjUXh49Zox6ZWdbAdrol9fPE7orKyv1rzvv1N9vuUULFizQyy+9pHhF6EZAjU6b3lzdS2+uztae/as1Y2yx7tpro66dVKgnluToheW9Vd6QwKcHAAAAbCMTuN85bHWXfY637h5ap2ZHzBmmBSWpnapibnosNx64975W62/661+0ZfNmq1R869atVuj+9yMPKzHJN36OnzBBTU1NVrNiUyW9pqZG8YjQjXbY9NXmDOsxsledzh9TrFkTi3Tp+K16aUUvK4Cvr2o9Xh0AAACA0JgSZxOAQynpDiVQX/dN/5BLujvjh++/t/raMkNp/fDDDz7rsrKyNHToUN1917+04NdfrWU77rhjq2OMHTdOJ518sm7605907nnnWSH+jttuVzwidCNky8pSdO03A3X7z3lWh2tnjCzRWaNKNGddllX1PJT/sAEAAAD4MlW8QylxXlSaot+P39pum+6XV/bu8jbdPq/jcOiC86d7pr2ZauOmCrrpBLu4uNjqGPuc5vbfbmlpabr6mqv15htv6vvvvldRYZHuvu9eq3335599rngTc+N0I/LMcGJ3/JKnPd/YwRqWYGJurd46dI1eOmiNDhpcIZuiu4M6AAAAIBaZIG1+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", 244 | "text/plain": [ 245 | "
" 246 | ] 247 | }, 248 | "metadata": {}, 249 | "output_type": "display_data" 250 | } 251 | ], 252 | "source": [ 253 | "dfw = pd.DataFrame(rows).sort_values(\"window\")\n", 254 | "for col in [\n", 255 | " \"Min\",\n", 256 | " \"Max\",\n", 257 | " \"Median\",\n", 258 | " f\"{quantiles[1]*100:.0f} Prctl\",\n", 259 | " f\"{quantiles[0]*100:.0f} Prctl\",\n", 260 | " \"Realized\",\n", 261 | "]:\n", 262 | " plt.plot(\n", 263 | " dfw[\"window\"].to_numpy(), dfw[col].to_numpy(), \"-o\", linewidth=1, label=col\n", 264 | " )\n", 265 | "plt.legend()\n", 266 | "plt.xlabel(\"Window\")\n", 267 | "plt.ylabel(\"Annualized σ\")\n", 268 | "plt.xticks(windows)\n", 269 | "plt.show()" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "id": "aaa04251", 275 | "metadata": {}, 276 | "source": [ 277 | "Read the cone across the x-axis by tenor, not as a time series. If today’s realized is near the lower band while same-tenor implied is high, selling optionality can be justified; the reverse suggests caution or buying vol. Always layer in event context like earnings or macro releases because cones anchor expectations but do not predict jumps." 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "id": "c6f34435", 283 | "metadata": {}, 284 | "source": [ 285 | "PyQuant News 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 get started with Python for quant finance. For educational purposes. Not investment advice. Use at your own risk." 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": null, 291 | "id": "2b23dc9a-12e5-4745-9831-930dcf89b46d", 292 | "metadata": {}, 293 | "outputs": [], 294 | "source": [] 295 | } 296 | ], 297 | "metadata": { 298 | "jupytext": { 299 | "cell_metadata_filter": "-all", 300 | "main_language": "python", 301 | "notebook_metadata_filter": "-all" 302 | }, 303 | "kernelspec": { 304 | "display_name": "Python 3 (ipykernel)", 305 | "language": "python", 306 | "name": "python3" 307 | }, 308 | "language_info": { 309 | "codemirror_mode": { 310 | "name": "ipython", 311 | "version": 3 312 | }, 313 | "file_extension": ".py", 314 | "mimetype": "text/x-python", 315 | "name": "python", 316 | "nbconvert_exporter": "python", 317 | "pygments_lexer": "ipython3", 318 | "version": "3.12.9" 319 | } 320 | }, 321 | "nbformat": 4, 322 | "nbformat_minor": 5 323 | } 324 | --------------------------------------------------------------------------------