{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4d2d5f0f",
   "metadata": {},
   "source": [
    "<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e2cdebe",
   "metadata": {},
   "source": [
    "This code implements a momentum-based trading strategy using Zipline, a backtesting library for Python. It calculates the Simple Moving Average (SMA) of selected ETFs over a 10-month period. The strategy rebalances the portfolio at the start of each month, investing in ETFs that are above their SMA. It also sets realistic commission and slippage values. The final performance of the strategy is evaluated using Pyfolio for detailed analysis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4454d728",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "abcc9c9f",
   "metadata": {},
   "outputs": [],
   "source": [
    "warnings.filterwarnings(\"ignore\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a51de9e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import pandas_datareader.data as web"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "238b106c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from zipline import run_algorithm\n",
    "from zipline.api import (\n",
    "    attach_pipeline,\n",
    "    date_rules,\n",
    "    order_target_percent,\n",
    "    pipeline_output,\n",
    "    record,\n",
    "    schedule_function,\n",
    "    symbol,\n",
    "    time_rules,\n",
    "    get_open_orders,\n",
    ")\n",
    "from zipline.finance import commission, slippage\n",
    "from zipline.pipeline import Pipeline\n",
    "from zipline.pipeline.factors import SimpleMovingAverage\n",
    "from zipline.pipeline.data import USEquityPricing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1a0027e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pyfolio as pf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33f4ed29",
   "metadata": {},
   "outputs": [],
   "source": [
    "def initialize(context):\n",
    "    \"\"\"\n",
    "    Initialization function for setting up the algorithm.\n",
    "\n",
    "    Defines the ETFs to be traded, sets the SMA period, and schedules the rebalance function.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    context : object\n",
    "        A context object holding the state of the algorithm.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    None\n",
    "    \"\"\"\n",
    "\n",
    "    context.symbols = [\n",
    "        symbol(\"SPY\"),\n",
    "        symbol(\"EFA\"),\n",
    "        symbol(\"IEF\"),\n",
    "        symbol(\"VNQ\"),\n",
    "        symbol(\"GSG\"),\n",
    "    ]\n",
    "\n",
    "    # Create an empty dictionary to store Simple Moving Average values for each ETF\n",
    "\n",
    "    context.sma = {}\n",
    "\n",
    "    # Define the SMA period as 10 months (approximately 21 trading days per month)\n",
    "\n",
    "    context.period = 10 * 21\n",
    "\n",
    "    # Calculate the SMA for each ETF over the defined period\n",
    "\n",
    "    for asset in context.symbols: \n",
    "        context.sma[asset] = SimpleMovingAverage(\n",
    "            inputs=[USEquityPricing.close],\n",
    "            window_length=context.period\n",
    "        )\n",
    "    \n",
    "    # Schedule the rebalance function to run at the start of each month, one minute after market opens\n",
    "\n",
    "    schedule_function(\n",
    "        func=rebalance,\n",
    "        date_rule=date_rules.month_start(),\n",
    "        time_rule=time_rules.market_open(minutes=1),\n",
    "    )\n",
    "\n",
    "    # Set commission and slippage to realistic values\n",
    "\n",
    "    context.set_commission(\n",
    "        commission.PerShare(cost=0.01, min_trade_cost=1.00)\n",
    "    )\n",
    "    context.set_slippage(slippage.VolumeShareSlippage())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc9a0b79",
   "metadata": {},
   "outputs": [],
   "source": [
    "def rebalance(context, data):\n",
    "    \"\"\"\n",
    "    Rebalance function to be run at the start of each month.\n",
    "\n",
    "    Adjusts the portfolio by closing positions not in the 'longs' list and setting target percentages for 'longs'.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    context : object\n",
    "        A context object holding the state of the algorithm.\n",
    "    data : object\n",
    "        A data object providing market data.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    None\n",
    "    \"\"\"\n",
    "\n",
    "    longs = [\n",
    "        asset\n",
    "        for asset in context.symbols\n",
    "        if data.current(asset, \"price\") > context.sma[asset].mean()\n",
    "    ]\n",
    "\n",
    "    # Close positions for assets not in 'longs'\n",
    "\n",
    "    for asset in context.portfolio.positions:\n",
    "        if asset not in longs and data.can_trade(asset):\n",
    "            order_target_percent(asset, 0)\n",
    "\n",
    "    # Set target portfolio percentage for each asset in 'longs'\n",
    "\n",
    "    for asset in longs:\n",
    "        if data.can_trade(asset):\n",
    "            order_target_percent(asset, 1.0 / len(longs))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a857b85",
   "metadata": {},
   "outputs": [],
   "source": [
    "start = pd.Timestamp(\"2010\")\n",
    "end = pd.Timestamp(\"2023-06-30\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e5cce708",
   "metadata": {},
   "outputs": [],
   "source": [
    "sp500 = web.DataReader('SP500', 'fred', start, end).SP500\n",
    "benchmark_returns = sp500.pct_change()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9fd980d",
   "metadata": {},
   "source": [
    "Run the algorithm with the specified parameters and capture performance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff897820",
   "metadata": {},
   "outputs": [],
   "source": [
    "perf = run_algorithm(\n",
    "    start=start,\n",
    "    end=end,\n",
    "    initialize=initialize,\n",
    "    capital_base=100000,\n",
    "    bundle='quandl-eod'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "564a63a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "perf"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b1b0608",
   "metadata": {},
   "source": [
    "Extract returns, positions, and transactions from the performance DataFrame"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9f8e96d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "returns, positions, transactions = \\\n",
    "    pf.utils.extract_rets_pos_txn_from_zipline(perf)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff8dfb4f",
   "metadata": {},
   "source": [
    "Create a full tear sheet to analyze the performance of the strategy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "06a56fb8",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf.create_full_tear_sheet(\n",
    "    returns,\n",
    "    positions=positions,\n",
    "    transactions=transactions,\n",
    "    round_trips=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3be746eb",
   "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"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
