{ "cells": [ { "cell_type": "markdown", "id": "e8e4c152", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "id": "a57f8b45", "metadata": {}, "source": [ "This code downloads historical stock data for SPY and AAPL from Yahoo Finance and calculates daily returns. It then computes the Sortino ratio, a risk-adjusted performance metric, for these returns. The Sortino ratio focuses on downside risk, providing a better measure for evaluating strategies with asymmetric risk profiles. The code also visualizes the rolling Sortino ratio and its distribution for AAPL. This is useful for performance analysis and comparison of different investment strategies." ] }, { "cell_type": "code", "execution_count": null, "id": "8a9c9a42", "metadata": {}, "outputs": [], "source": [ "import yfinance as yf\n", "import numpy as np" ] }, { "cell_type": "markdown", "id": "aac23142", "metadata": {}, "source": [ "Download historical adjusted closing prices for SPY and AAPL from Yahoo Finance" ] }, { "cell_type": "code", "execution_count": null, "id": "e3627e86", "metadata": {}, "outputs": [], "source": [ "data = yf.download([\"SPY\", \"AAPL\"], start=\"2020-01-01\", end=\"2022-07-31\")" ] }, { "cell_type": "markdown", "id": "778fee89", "metadata": {}, "source": [ "Extract adjusted closing prices and calculate daily returns for SPY and AAPL" ] }, { "cell_type": "code", "execution_count": null, "id": "41003991", "metadata": { "lines_to_next_cell": 1 }, "outputs": [], "source": [ "closes = data['Adj Close']\n", "spy_returns = closes.SPY.pct_change().dropna()\n", "aapl_returns = closes.AAPL.pct_change().dropna()" ] }, { "cell_type": "markdown", "id": "dbb043d7", "metadata": {}, "source": [ "Define a function to calculate the Sortino ratio for a series of returns" ] }, { "cell_type": "code", "execution_count": null, "id": "fefe6265", "metadata": { "lines_to_next_cell": 1 }, "outputs": [], "source": [ "def sortino_ratio(returns, adjustment_factor=0.0):\n", " \"\"\"\n", " Determines the Sortino ratio of a strategy.\n", " \n", " Parameters\n", " ----------\n", " returns : pd.Series or np.ndarray\n", " Daily returns of the strategy, noncumulative.\n", " adjustment_factor : int, float\n", " Constant daily benchmark return throughout the period.\n", "\n", " Returns\n", " -------\n", " sortino_ratio : float\n", "\n", " Note\n", " -----\n", " See ``__ for more details.\n", " \"\"\"\n", "\n", " # Adjust returns by subtracting the adjustment factor and compute average annual return\n", " returns_risk_adj = np.asanyarray(returns - adjustment_factor)\n", " average_annual_return = returns_risk_adj.mean() * 252\n", "\n", " # Compute downside deviation by considering only negative deviations\n", " downside_diff = np.clip(returns_risk_adj, np.NINF, 0)\n", " np.square(downside_diff, out=downside_diff)\n", " annualized_downside_deviation = np.sqrt(downside_diff.mean()) * np.sqrt(252)\n", "\n", " # Calculate and return the Sortino ratio\n", " return average_annual_return / annualized_downside_deviation" ] }, { "cell_type": "markdown", "id": "334d33ea", "metadata": {}, "source": [ "Calculate the Sortino ratio for SPY's daily returns" ] }, { "cell_type": "code", "execution_count": null, "id": "0c281e5e", "metadata": {}, "outputs": [], "source": [ "sortino_ratio(spy_returns)" ] }, { "cell_type": "markdown", "id": "cd306508", "metadata": {}, "source": [ "Calculate the Sortino ratio for AAPL's daily returns" ] }, { "cell_type": "code", "execution_count": null, "id": "a6d8b4a1", "metadata": {}, "outputs": [], "source": [ "sortino_ratio(aapl_returns)" ] }, { "cell_type": "markdown", "id": "ea3d422f", "metadata": {}, "source": [ "Plot the rolling 30-day Sortino ratio for AAPL's returns" ] }, { "cell_type": "code", "execution_count": null, "id": "b86b1ba0", "metadata": {}, "outputs": [], "source": [ "aapl_returns.rolling(30).apply(sortino_ratio).plot()" ] }, { "cell_type": "markdown", "id": "bf0f5718", "metadata": {}, "source": [ "Plot the histogram of the rolling 30-day Sortino ratio for AAPL's returns" ] }, { "cell_type": "code", "execution_count": null, "id": "dcf47150", "metadata": {}, "outputs": [], "source": [ "aapl_returns.rolling(30).apply(sortino_ratio).hist(bins=50)" ] }, { "cell_type": "markdown", "id": "8eaa8c01", "metadata": {}, "source": [ "Plot the histogram of the difference between rolling 30-day Sortino ratios of AAPL and SPY" ] }, { "cell_type": "code", "execution_count": null, "id": "3f3b8abb", "metadata": {}, "outputs": [], "source": [ "(\n", " aapl_returns.rolling(30).apply(sortino_ratio)\n", " - spy_returns.rolling(30).apply(sortino_ratio)\n", ").hist(bins=50)" ] }, { "cell_type": "markdown", "id": "7ffcdc8a", "metadata": {}, "source": [ "Calculate the Sortino ratio for SPY's daily returns again" ] }, { "cell_type": "code", "execution_count": null, "id": "221fcbf5", "metadata": {}, "outputs": [], "source": [ "sortino_ratio(spy_returns)" ] }, { "cell_type": "markdown", "id": "ac229400", "metadata": {}, "source": [ "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 advise. Use at your own risk." ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" } }, "nbformat": 4, "nbformat_minor": 5 }