{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ab3c3685",
   "metadata": {},
   "source": [
    "<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1873e801",
   "metadata": {},
   "source": [
    "This code downloads historical price data for the TLT ETF and generates trading signals based on calendar rules. It sets up short and long entry and exit signals, such as entering a short position on the first day of each month and exiting five days later. The code also defines a function to simulate a trading strategy and calculate performance metrics, specifically the Sharpe ratio. Finally, it runs this strategy on shuffled data to generate a distribution of Sharpe ratios for comparison. This approach aids in evaluating the robustness and statistical significance of the trading strategy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11c2ac72",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import vectorbt as vbt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "baa26e68",
   "metadata": {},
   "source": [
    "Download historical price data for TLT ETF from Yahoo Finance and extract the closing prices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ecf3f8f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "tlt = vbt.YFData.download(\n",
    "    \"TLT\", \n",
    "    start=\"2004-01-01\"\n",
    ").get(\"Close\").to_frame()\n",
    "close = tlt.Close"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0192959f",
   "metadata": {},
   "source": [
    "Set up empty dataframes to hold trading signals for short and long positions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c5aa36b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "short_entries = pd.DataFrame.vbt.signals.empty_like(close)\n",
    "short_exits = pd.DataFrame.vbt.signals.empty_like(close)\n",
    "long_entries = pd.DataFrame.vbt.signals.empty_like(close)\n",
    "long_exits = pd.DataFrame.vbt.signals.empty_like(close)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5404f63",
   "metadata": {},
   "source": [
    "Generate short entry signals on the first day of each new month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a37dc3c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "short_entry_mask = ~tlt.index.tz_convert(None).to_period(\"M\").duplicated()\n",
    "short_entries.iloc[short_entry_mask] = True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79e3c619",
   "metadata": {},
   "source": [
    "Generate short exit signals five days after short entry"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fb2d80b",
   "metadata": {},
   "outputs": [],
   "source": [
    "short_exit_mask = short_entries.shift(5).fillna(False)\n",
    "short_exits.iloc[short_exit_mask] = True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d014794e",
   "metadata": {},
   "source": [
    "Generate long entry signals seven days before the end of each month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ebf6e5e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "long_entry_mask = short_entries.shift(-7).fillna(False)\n",
    "long_entries.iloc[long_entry_mask] = True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16a2dce9",
   "metadata": {},
   "source": [
    "Generate long exit signals one day before the end of each month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22160cf9",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "long_exit_mask = short_entries.shift(-1).fillna(False)\n",
    "long_exits.iloc[long_exit_mask] = True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52de0bee",
   "metadata": {},
   "source": [
    "Define a function to simulate the trading strategy and calculate performance metrics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2df090a8",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "def run_sim(close):\n",
    "    \"\"\"Simulate trading strategy\n",
    "\n",
    "    This function simulates a trading strategy based on given entry and exit signals.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    close : pd.Series\n",
    "        The closing prices of the asset.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    pf : vbt.Portfolio\n",
    "        The simulated portfolio.\n",
    "    \"\"\"\n",
    "    \n",
    "    return vbt.Portfolio.from_signals(\n",
    "        close=close,\n",
    "        entries=long_entries,\n",
    "        exits=long_exits,\n",
    "        short_entries=short_entries,\n",
    "        short_exits=short_exits,\n",
    "        freq=\"1d\"\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7afb4498",
   "metadata": {},
   "source": [
    "Run the simulation and calculate the Sharpe ratio for the trading strategy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "241a38f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = run_sim(close)\n",
    "sr = pf.sharpe_ratio()\n",
    "pf.stats()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff347e51",
   "metadata": {},
   "source": [
    "Run the simulation on shuffled data 1000 times to generate a distribution of Sharpe ratios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7761864b",
   "metadata": {},
   "outputs": [],
   "source": [
    "sharpes = []\n",
    "for i in range(1000):\n",
    "    shuffled_close = close.sample(frac=1)\n",
    "    shuffled_close.index = close.index\n",
    "    shuffled_sharpe = run_sim(shuffled_close).sharpe_ratio()\n",
    "    sharpes.append(shuffled_sharpe)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e7613a8",
   "metadata": {},
   "source": [
    "Plot the distribution of simulated Sharpe ratios and mark the original strategy's Sharpe ratio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff9c8df9",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.DataFrame(\n",
    "    sharpes, \n",
    "    columns=[\"Simulated Sharpe Ratios\"]\n",
    ").hist(\n",
    "    bins=np.histogram_bin_edges(sharpes, bins=\"fd\")\n",
    ")\n",
    "plt.axvline(sr, linestyle=\"dashed\", linewidth=1)\n",
    "min_ylim, max_ylim = plt.ylim()\n",
    "plt.text(sr * -0.01, max_ylim * 0.9, f\"Strat Sharpe: {sr:.1f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c8c6d66",
   "metadata": {},
   "source": [
    "<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
