{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Beta hedging with Fonrex\n",
    "\n",
    "This notebook builds an equally weighted portfolio of gold stocks and healthcare stocks, measures its alpha and beta against the S&P 500 (SPY), then builds a beta-hedged portfolio.\n",
    "\n",
    "The daily prices come from **your own Fonrex instance** (`GET /eod/{ticker}`) instead of OpenBB.\n",
    "\n",
    "**Before you run it**\n",
    "\n",
    "1. A Fonrex instance is running (`docker compose up -d`).\n",
    "2. The nine listings are in the catalogue, in US dollars (see the guide *Using Fonrex from Python and Jupyter*).\n",
    "   Your Fonrex version accepts the `isin` parameter of `GET /eod`.\n",
    "3. Set your key in the environment before starting Jupyter: `export FONREX_API_KEY=frx_live_...`. A read-only key is enough."
   ],
   "id": "cell-00"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "%pip install statsmodels requests"
   ],
   "id": "cell-01"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import os\n",
    "import warnings\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import requests\n",
    "import statsmodels.api as sm\n",
    "\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "\n",
    "FONREX_URL = os.environ.get(\"FONREX_URL\", \"http://localhost:5000\")\n",
    "FONREX_API_KEY = os.environ[\"FONREX_API_KEY\"]"
   ],
   "id": "cell-02"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`fonrex_prices()` replaces `obb.equity.price.historical(...).pivot(columns=\"symbol\", values=\"close\")`: one column per symbol, one row per trading session.\n",
    "\n",
    "A ticker alone does not always designate one instrument: in the catalogue, `NEM` is also Nemetschek, `MRK` also Merck KGaA and `CDE` also City Developments. Each ticker comes with the **ISIN** of its instrument, and `currency=\"USD\"` chooses its US listing."
   ],
   "id": "cell-03"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def fonrex_prices(instruments, start_date, end_date, currency=\"USD\"):\n",
    "    \"\"\"Daily closing prices of several listings, one column per ticker.\n",
    "\n",
    "    ``instruments`` maps each ticker to the ISIN of its instrument.\n",
    "    \"\"\"\n",
    "    session = requests.Session()\n",
    "    session.headers[\"X-API-KEY\"] = FONREX_API_KEY\n",
    "    closes = {}\n",
    "    for ticker, isin in instruments.items():\n",
    "        response = session.get(\n",
    "            f\"{FONREX_URL}/eod/{ticker}\",\n",
    "            params={\"from\": start_date, \"to\": end_date, \"isin\": isin, \"currency\": currency},\n",
    "            timeout=120,  # the first call ingests the prices from Yahoo Finance\n",
    "        )\n",
    "        if response.status_code != 200:\n",
    "            raise RuntimeError(f\"{ticker}: {response.status_code} {response.text}\")\n",
    "        bars = pd.DataFrame(response.json()[\"data\"])\n",
    "        closes[ticker] = bars.set_index(pd.to_datetime(bars[\"Date\"]))[\"Close\"]\n",
    "    data = pd.DataFrame(closes).sort_index(axis=1)\n",
    "    data.index.name = \"date\"\n",
    "    data.columns.name = \"symbol\"\n",
    "    return data"
   ],
   "id": "cell-04"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Construct an equally weighted portfolio of gold stocks (NEM, RGLD, SSRM, CDE) and healthcare stocks (LLY, UNH, JNJ, MRK)."
   ],
   "id": "cell-05"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "instruments = {\n",
    "    \"NEM\": \"US6516391066\",   # Newmont\n",
    "    \"RGLD\": \"US7802871084\",  # Royal Gold\n",
    "    \"SSRM\": \"CA7847301032\",  # SSR Mining\n",
    "    \"CDE\": \"US1921085049\",   # Coeur Mining\n",
    "    \"LLY\": \"US5324571083\",   # Eli Lilly\n",
    "    \"UNH\": \"US91324P1021\",   # UnitedHealth\n",
    "    \"JNJ\": \"US4781601046\",   # Johnson & Johnson\n",
    "    \"MRK\": \"US58933Y1055\",   # Merck & Co\n",
    "    \"SPY\": \"US78462F1030\",   # SPDR S&P 500 ETF\n",
    "}\n",
    "data = fonrex_prices(instruments, start_date=\"2020-01-01\", end_date=\"2022-12-31\")"
   ],
   "id": "cell-06"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "data"
   ],
   "id": "cell-07"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Extract the benchmark returns (SPY) and compute the daily returns."
   ],
   "id": "cell-08"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "benchmark_returns = (\n",
    "    data\n",
    "    .pop(\"SPY\")\n",
    "    .pct_change()\n",
    "    .dropna()\n",
    ")\n",
    "benchmark_returns"
   ],
   "id": "cell-09"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Compute the portfolio returns by summing the daily returns of each asset."
   ],
   "id": "cell-10"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "portfolio_returns = (\n",
    "    data\n",
    "    .pct_change()\n",
    "    .dropna()\n",
    "    .sum(axis=1)\n",
    ")\n",
    "portfolio_returns.name = \"portfolio\"\n",
    "portfolio_returns"
   ],
   "id": "cell-11"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Plot the portfolio returns against the benchmark returns."
   ],
   "id": "cell-12"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "portfolio_returns.plot()\n",
    "benchmark_returns.plot()\n",
    "plt.ylabel(\"Daily Return\")\n",
    "plt.legend()"
   ],
   "id": "cell-13"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Regress the portfolio returns on the benchmark returns to estimate alpha and beta."
   ],
   "id": "cell-14"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "X = benchmark_returns.values\n",
    "Y = portfolio_returns.values\n",
    "\n",
    "\n",
    "def linreg(x, y):\n",
    "    # Add a column of 1s to fit alpha\n",
    "    x = sm.add_constant(x)\n",
    "    model = sm.OLS(y, x).fit()\n",
    "    return model.params[0], model.params[1]\n",
    "\n",
    "\n",
    "alpha, beta = linreg(X, Y)\n",
    "print(f\"Alpha: {alpha}\")\n",
    "print(f\"Beta: {beta}\")"
   ],
   "id": "cell-15"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Show the returns with the regression line."
   ],
   "id": "cell-16"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "X2 = np.linspace(X.min(), X.max(), 100)\n",
    "Y_hat = alpha + X2 * beta\n",
    "\n",
    "plt.scatter(X, Y, alpha=0.3)\n",
    "plt.plot(X2, Y_hat, \"r\", alpha=0.9)\n",
    "plt.xlabel(\"SPY daily return\")\n",
    "plt.ylabel(\"Portfolio daily return\")"
   ],
   "id": "cell-17"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Build a beta-hedged portfolio to neutralise market movements."
   ],
   "id": "cell-18"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "hedged_portfolio_returns = -beta * benchmark_returns + portfolio_returns\n",
    "hedged_portfolio_returns.name = \"Hedged portfolio\""
   ],
   "id": "cell-19"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Regress the hedged portfolio: its beta is now about zero."
   ],
   "id": "cell-20"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "P = hedged_portfolio_returns.values\n",
    "alpha, beta = linreg(X, P)\n",
    "print(f\"Alpha: {alpha}\")\n",
    "print(f\"Beta: {round(beta, 6)}\")"
   ],
   "id": "cell-21"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "hedged_portfolio_returns.plot()\n",
    "benchmark_returns.plot()\n",
    "plt.ylabel(\"Daily Return\")\n",
    "plt.legend()"
   ],
   "id": "cell-22"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
