{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9b76874f-1446-4198-8695-386bbb22060f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectorbtpro import *\n",
    "# whats_imported()\n",
    "\n",
    "vbt.settings.set_theme(\"dark\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db16b4c1-1ffb-41fe-a04b-249161b80b3f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# data = vbt.BinanceData.pull(\n",
    "#     [\"BTCUSDT\", \"ETHUSDT\", \"BNBUSDT\", \"XRPUSDT\", \"ADAUSDT\"], \n",
    "#     start=\"2020-01-01 UTC\", \n",
    "#     end=\"2021-01-01 UTC\",\n",
    "#     timeframe=\"1h\"\n",
    "# )\n",
    "\n",
    "# data.to_hdf()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd78fe86-080e-49ab-bfd3-87634465cc86",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = vbt.HDFData.pull(\"BinanceData.h5\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69590f7f-bde5-428c-a036-52d6bd207a72",
   "metadata": {},
   "source": [
    "## Allocation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bec503b-c470-4255-aaf9-22cb67037586",
   "metadata": {},
   "source": [
    "### Manually"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ca8b280-a993-4501-b00e-01dbc86ceb21",
   "metadata": {},
   "source": [
    "#### Index points"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc61be80-bc27-42be-80bf-8335285eef96",
   "metadata": {},
   "outputs": [],
   "source": [
    "ms_points = data.wrapper.get_index_points(every=\"M\")\n",
    "ms_points"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ae7a4d6-ea1f-45ef-9653-3e0def80e242",
   "metadata": {},
   "outputs": [],
   "source": [
    "data.wrapper.index.get_indexer(\n",
    "    pd.Series(index=data.wrapper.index).resample(vbt.offset(\"M\")).asfreq().index, \n",
    "    method=\"bfill\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b7a1475a-7cf1-42b9-aa29-454591557ce3",
   "metadata": {},
   "outputs": [],
   "source": [
    "data.wrapper.index[ms_points]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc620982-9a4b-4176-9d13-6088acac0ce6",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_points = data.wrapper.get_index_points(every=24 * 30)\n",
    "data.wrapper.index[example_points]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a43d5382-fe93-444d-ab7a-04736ab4b8e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "date_offset = pd.offsets.WeekOfMonth(week=3, weekday=4)\n",
    "example_points = data.wrapper.get_index_points(\n",
    "    every=date_offset, \n",
    "    add_delta=pd.Timedelta(hours=17)\n",
    ")\n",
    "data.wrapper.index[example_points]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6f534de-68f6-4e3c-b55c-2d80ac197d8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_points = data.wrapper.get_index_points(\n",
    "    start=\"April 1st 2020\",\n",
    "    every=\"M\"\n",
    ")\n",
    "data.wrapper.index[example_points]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85ab2969-6a78-4869-8fcb-f498cd6430b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_points = data.wrapper.get_index_points(\n",
    "    on=[\"April 1st 2020 19:45\", \"17 September 2020 00:01\"]\n",
    ")\n",
    "data.wrapper.index[example_points]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5681f6ee-bb4b-473a-aaec-92af48e290ee",
   "metadata": {},
   "source": [
    "#### Filling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4c86c70-3611-43c7-8536-2c2b4a903d35",
   "metadata": {},
   "outputs": [],
   "source": [
    "symbol_wrapper = data.get_symbol_wrapper(freq=\"1h\")\n",
    "filled_allocations = symbol_wrapper.fill()\n",
    "print(filled_allocations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e81dcdf2-0079-4adb-9b15-99486a56fc4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(42)\n",
    "\n",
    "def random_allocate_func():\n",
    "    weights = np.random.uniform(size=symbol_wrapper.shape[1])\n",
    "    return weights / weights.sum()\n",
    "\n",
    "for idx in ms_points:\n",
    "    filled_allocations.iloc[idx] = random_allocate_func()\n",
    "\n",
    "allocations = filled_allocations[~filled_allocations.isnull().any(axis=1)]\n",
    "allocations"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb975a93-4739-4f3a-b3ba-068cdf82d27e",
   "metadata": {},
   "source": [
    "#### Simulation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24345fec-cc7a-4bc1-a822-4794b5bd8995",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = vbt.Portfolio.from_orders(\n",
    "    close=data.get(\"Close\"),\n",
    "    size=filled_allocations,\n",
    "    size_type=\"targetpercent\",\n",
    "    group_by=True,\n",
    "    cash_sharing=True,\n",
    "    call_seq=\"auto\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40e232aa-6f52-41ff-b7bb-c8f073ced4fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "sim_alloc = pf.get_asset_value(group_by=False).vbt / pf.value\n",
    "print(sim_alloc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5817b120-0903-46a8-b96a-9e40b46e05be",
   "metadata": {},
   "outputs": [],
   "source": [
    "sim_alloc.vbt.plot(\n",
    "   trace_kwargs=dict(stackgroup=\"one\"),\n",
    "   use_gl=False\n",
    ").show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a2b2fa9e-4261-4f3e-b0fc-621567101a86",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf.plot_allocations().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3382d027-387f-4c6d-bd1c-e1e83ebe97e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.isclose(allocations, sim_alloc.iloc[ms_points])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6aa7e781-88cd-4fb9-b492-f249207d1d97",
   "metadata": {},
   "source": [
    "### Allocation method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d38deafd-ca0a-487a-8109-cf99c6eb63fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(42)\n",
    "\n",
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    random_allocate_func,\n",
    "    every=\"M\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd07c105-1448-41b7-918d-209129790757",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pfo.allocations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e1f5f65c-efd5-45e8-869b-f0f208054ec7",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pfo.filled_allocations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9309c8f5-79a7-477b-8e19-7953a86df612",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pfo.alloc_records.records_readable)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77ca4765-ea6e-40d5-8f51-25ded1e76168",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.plot().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18d794b3-f011-42e7-a7e2-300cf9f3b195",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.stats()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "185132cb-120f-43c1-bf07-72b382e273ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = vbt.Portfolio.from_optimizer(data, pfo, freq=\"1h\")\n",
    "\n",
    "pf.sharpe_ratio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a398f1b4-71b8-4f67-8ce1-fd9b5cc56d56",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = pfo.simulate(data, freq=\"1h\")\n",
    "\n",
    "pf.sharpe_ratio"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82efb592-393e-4c9e-9d7a-2c50c8d3fb44",
   "metadata": {},
   "source": [
    "#### Once"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2db279f6-4ebf-4f8c-82a2-a2cf820b740d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def const_allocate_func(target_alloc):\n",
    "    return target_alloc\n",
    "\n",
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    const_allocate_func,\n",
    "    [0.5, 0.2, 0.1, 0.1, 0.1]\n",
    ")\n",
    "\n",
    "pfo.plot().show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8fef0f6-976a-468a-8337-a9fc59847f96",
   "metadata": {},
   "source": [
    "#### Parsing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5dbd26d9-8953-4f3d-9844-68c035a3d264",
   "metadata": {},
   "outputs": [],
   "source": [
    "custom_index = vbt.date_range(\"2020-01-01\", \"2021-01-01\", freq=\"Q\")\n",
    "custom_allocations = pd.DataFrame(\n",
    "    [\n",
    "        [0.5, 0.2, 0.1, 0.1, 0.1],\n",
    "        [0.1, 0.5, 0.2, 0.1, 0.1],\n",
    "        [0.1, 0.1, 0.5, 0.2, 0.1],\n",
    "        [0.1, 0.1, 0.1, 0.5, 0.2]\n",
    "    ],\n",
    "    index=custom_index, \n",
    "    columns=symbol_wrapper.columns\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c2e9dc9-60c4-40ee-a725-c6d5634a7c4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocations(\n",
    "    symbol_wrapper,\n",
    "    custom_allocations\n",
    ")\n",
    "print(pfo.allocations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fd3f29d5-ddab-4ba9-abd3-c09807c072f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocations(\n",
    "    symbol_wrapper,\n",
    "    custom_allocations.values,\n",
    "    start=\"2020-01-01\",\n",
    "    end=\"2021-01-01\",\n",
    "    every=\"Q\"\n",
    ")\n",
    "print(pfo.allocations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eeab75a5-6e4f-4751-b9e5-3fbaaf6717fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_filled_allocations(\n",
    "    pfo.fill_allocations()\n",
    ")\n",
    "print(pfo.allocations)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c254a91a-85d7-4502-a208-606a60f3d8a4",
   "metadata": {},
   "source": [
    "#### Templates"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "128d6a58-e133-476a-a78a-2ab2eede62e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "def rotation_allocate_func(wrapper, i):\n",
    "    weights = np.full(len(wrapper.columns), 0)\n",
    "    weights[i % len(wrapper.columns)] = 1\n",
    "    return weights\n",
    "\n",
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    rotation_allocate_func,\n",
    "    vbt.Rep(\"wrapper\"),\n",
    "    vbt.Rep(\"i\"),\n",
    "    every=\"M\"\n",
    ")\n",
    "\n",
    "pfo.plot().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0fc4975-fb15-4d56-a42e-f9a3686441fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "def rotation_allocate_func(symbols, chosen_symbol):\n",
    "    return {s: 1 if s == chosen_symbol else 0 for s in symbols}\n",
    "\n",
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    rotation_allocate_func,\n",
    "    vbt.RepEval(\"wrapper.columns\"),\n",
    "    vbt.RepEval(\"wrapper.columns[i % len(wrapper.columns)]\"),\n",
    "    every=\"M\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0cac3042-ab4b-4f88-b361-5381b619e91e",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pfo.allocations)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48324866-77d0-4743-9778-4468c6933941",
   "metadata": {},
   "source": [
    "#### Groups"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "849f130f-aaaa-4b84-9e3d-002be887c103",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    const_allocate_func,\n",
    "    [0.5, 0.2, 0.1, 0.1, 0.1],\n",
    "    every=vbt.Param([\"1M\", \"2M\", \"3M\"])\n",
    ")\n",
    "\n",
    "pf = pfo.simulate(data, freq=\"1h\")\n",
    "pf.total_return"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52154cba-8a20-43bf-934e-efcf8a07d14a",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    const_allocate_func,\n",
    "    vbt.Param([\n",
    "        [0.5, 0.2, 0.1, 0.1, 0.1],\n",
    "        [0.2, 0.1, 0.1, 0.1, 0.5]\n",
    "    ], keys=pd.Index([\"w1\", \"w2\"], name=\"weights\")),\n",
    "    every=vbt.Param([\"1M\", \"2M\", \"3M\"])\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3fcfe8e6-a6b3-4904-9e9a-44612af115ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.wrapper.grouper.get_index()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "502b18b1-498a-459e-a11e-30b5e895e2ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.wrapper.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64d0fa79-4c39-4b30-99e9-afeb05f496e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo[(\"3M\", \"w2\")].stats()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1b03be1e-b989-4821-bd90-e4a4f9b9fd14",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    const_allocate_func,\n",
    "    group_configs=[\n",
    "        dict(args=([0.5, 0.2, 0.1, 0.1, 0.1],), every=\"1M\"),\n",
    "        dict(args=([0.2, 0.1, 0.1, 0.1, 0.5],), every=\"2M\"),\n",
    "        dict(args=([0.1, 0.1, 0.1, 0.5, 0.2],), every=\"3M\"),\n",
    "        dict(args=([0.1, 0.1, 0.5, 0.2, 0.1],), every=\"1M\"),\n",
    "        dict(args=([0.1, 0.5, 0.2, 0.1, 0.1],), every=\"2M\"),\n",
    "        dict(args=([0.5, 0.2, 0.1, 0.1, 0.1],), every=\"3M\"),\n",
    "    ]\n",
    ")\n",
    "pfo.wrapper.grouper.get_index()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "286c19a7-487d-4461-9ac7-505408df2d00",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    const_allocate_func,\n",
    "    group_configs=[\n",
    "        dict(\n",
    "            allocate_func=const_allocate_func, \n",
    "            args=([0.5, 0.2, 0.1, 0.1, 0.1],),\n",
    "            _name=\"const\"\n",
    "        ),\n",
    "        dict(\n",
    "            allocate_func=random_allocate_func,\n",
    "            every=\"M\",\n",
    "            _name=\"random\"\n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "pfo.wrapper.grouper.get_index()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d1370874-9993-4eb6-9561-8d4bdedcb93a",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    const_allocate_func,\n",
    "    group_configs={\n",
    "        \"const\": dict(\n",
    "            allocate_func=const_allocate_func, \n",
    "            args=([0.5, 0.2, 0.1, 0.1, 0.1],)\n",
    "        ),\n",
    "        \"random\": dict(\n",
    "            allocate_func=random_allocate_func,\n",
    "        ),\n",
    "    },\n",
    "    every=vbt.Param([\"1M\", \"2M\", \"3M\"])\n",
    ")\n",
    "pfo.wrapper.grouper.get_index()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "084666b9-43f5-459a-950c-12092771572e",
   "metadata": {},
   "source": [
    "#### Numba"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d1540dfe-a610-4cec-9acc-6f8f88585f76",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def rotation_allocate_func_nb(i, idx, n_cols):\n",
    "    weights = np.full(n_cols, 0)\n",
    "    weights[i % n_cols] = 1\n",
    "    return weights\n",
    "\n",
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    rotation_allocate_func_nb,\n",
    "    vbt.RepEval(\"len(wrapper.columns)\"),\n",
    "    every=\"W\",\n",
    "    jitted_loop=True\n",
    ")\n",
    "\n",
    "print(pfo.allocations.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6d5353d-99bd-4d8e-8bdd-fa8f32759b87",
   "metadata": {},
   "source": [
    "#### Distribution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fdc416bb-0457-4cb6-8838-9d9fce2e7551",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    rotation_allocate_func_nb,\n",
    "    vbt.Rep(\"i\"),\n",
    "    vbt.Rep(\"index_point\"),\n",
    "    vbt.RepEval(\"len(wrapper.columns)\"),\n",
    "    every=\"D\",\n",
    "    execute_kwargs=dict(engine=\"dask\")\n",
    ")\n",
    "\n",
    "print(pfo.allocations.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88153e42-fbfa-47b9-bdc0-31c786948fa2",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    rotation_allocate_func_nb,\n",
    "    vbt.RepEval(\"len(wrapper.columns)\"),\n",
    "    every=\"D\",\n",
    "    jitted_loop=True,\n",
    "    chunked=dict(\n",
    "        arg_take_spec=dict(args=vbt.ArgsTaker(None)),\n",
    "        engine=\"dask\"\n",
    "    )\n",
    ")\n",
    "\n",
    "print(pfo.allocations.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53abd71a-db25-46d9-b826-e4424bd713a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
    "    symbol_wrapper,\n",
    "    rotation_allocate_func_nb,\n",
    "    vbt.RepEval(\"len(wrapper.columns)\"),\n",
    "    every=\"D\",\n",
    "    jitted_loop=True,\n",
    "    jitted=dict(parallel=True)\n",
    ")\n",
    "\n",
    "print(pfo.allocations.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c40824e-6c4c-4ba9-a01e-0288132fbccd",
   "metadata": {},
   "source": [
    "## Optimization"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fc8fe78-7c3a-458d-8c71-fcb0697ab748",
   "metadata": {},
   "source": [
    "### Index ranges"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37bd154b-352e-431c-9958-276193afac4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_ranges = data.wrapper.get_index_ranges(every=\"M\")\n",
    "example_ranges[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ce4b815-90d1-4c66-a257-c7ca854375e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_ranges[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca7a3e1a-c959-4ec1-a39c-24b03bc77978",
   "metadata": {},
   "outputs": [],
   "source": [
    "data.wrapper.index[example_ranges[0][0]:example_ranges[1][0]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f655f008-a2b6-4861-b0b9-6eacc712f3a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_ranges = data.wrapper.get_index_ranges(\n",
    "    every=\"M\", \n",
    "    lookback_period=\"3M\"\n",
    ")\n",
    "\n",
    "def get_index_bounds(range_starts, range_ends):\n",
    "    for i in range(len(range_starts)):\n",
    "        range_index = data.wrapper.index[range_starts[i]:range_ends[i]]\n",
    "        yield range_index[0], range_index[-1]\n",
    "\n",
    "list(get_index_bounds(*example_ranges))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "533a7760-bf7a-4e85-92c6-5905ee4e7402",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_ranges = data.wrapper.get_index_ranges(\n",
    "    start=[\"2020-01-01\", \"2020-04-01\", \"2020-08-01\"],\n",
    "    end=[\"2020-04-01\", \"2020-08-01\", \"2020-12-01\"]\n",
    ")\n",
    "\n",
    "list(get_index_bounds(*example_ranges))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d990f69b-57e3-467a-96f7-4629e2efc3be",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_ranges = data.wrapper.get_index_ranges(\n",
    "    start=\"2020-01-01\",\n",
    "    end=[\"2020-04-01\", \"2020-08-01\", \"2020-12-01\"]\n",
    ")\n",
    "\n",
    "list(get_index_bounds(*example_ranges))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bf3e9132-1ad9-419c-a947-28d76605f0a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_ranges = data.wrapper.get_index_ranges(\n",
    "    every=\"Q\",\n",
    "    exact_start=True,\n",
    "    fixed_start=True\n",
    ")\n",
    "\n",
    "list(get_index_bounds(*example_ranges))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4eaa14c9-abd5-47c5-ab3e-4a567798f4d6",
   "metadata": {},
   "source": [
    "### Optimization method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "65f35ec4-46c1-4402-a74c-39a7ecf44d50",
   "metadata": {},
   "outputs": [],
   "source": [
    "def inv_rank_optimize_func(price, index_slice):\n",
    "    price_period = price.iloc[index_slice]\n",
    "    first_price = price_period.iloc[0]\n",
    "    last_price = price_period.iloc[-1]\n",
    "    ret = (last_price - first_price) / first_price\n",
    "    ranks = ret.rank(ascending=False)\n",
    "    return ranks / ranks.sum()\n",
    "\n",
    "pfo = vbt.PortfolioOptimizer.from_optimize_func(\n",
    "    symbol_wrapper,\n",
    "    inv_rank_optimize_func,\n",
    "    data.get(\"Close\"),\n",
    "    vbt.Rep(\"index_slice\"),\n",
    "    every=\"M\"\n",
    ")\n",
    "\n",
    "print(pfo.allocations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da8c39d5-31da-44cc-a384-0e270e8faea9",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pfo.alloc_records.records_readable)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d2e0254-6445-4bde-a162-99f77ed07e06",
   "metadata": {},
   "outputs": [],
   "source": [
    "start_idx = pfo.alloc_records.values[0][\"start_idx\"]\n",
    "end_idx = pfo.alloc_records.values[0][\"end_idx\"]\n",
    "close_period = data.get(\"Close\").iloc[start_idx:end_idx]\n",
    "close_period.vbt.rebase(1).vbt.plot().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f8f395a-a4bc-4d82-bb1e-ceb838c1dbd9",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.stats()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1bec9bf7-116d-4ee0-8f22-f5d85abb02be",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.plots().show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4cd5f118-7eff-444f-bcad-b0caed5563ac",
   "metadata": {},
   "source": [
    "#### Numba"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e7d1b93-cbe0-489a-833e-2421cdddf5cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def inv_rank_optimize_func_nb(i, start_idx, end_idx, price):\n",
    "    price_period = price[start_idx:end_idx]\n",
    "    first_price = price_period[0]\n",
    "    last_price = price_period[-1]\n",
    "    ret = (last_price - first_price) / first_price\n",
    "    ranks = vbt.nb.rank_1d_nb(-ret)\n",
    "    return ranks / ranks.sum()\n",
    "\n",
    "pfo = vbt.PortfolioOptimizer.from_optimize_func(\n",
    "    symbol_wrapper,\n",
    "    inv_rank_optimize_func_nb,\n",
    "    data.get(\"Close\").values,\n",
    "    every=\"M\",\n",
    "    jitted_loop=True\n",
    ")\n",
    "\n",
    "print(pfo.allocations)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f1cd1a0-beda-4a1d-ba6e-be353116c2a5",
   "metadata": {},
   "source": [
    "## Integrations"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b5eeddd-d48c-435e-afcd-032d56028605",
   "metadata": {},
   "source": [
    "### PyPortfolioOpt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eddbb356-a684-43cf-bfaf-3a024b297532",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypfopt.expected_returns import mean_historical_return\n",
    "from pypfopt.risk_models import CovarianceShrinkage\n",
    "from pypfopt.efficient_frontier import EfficientFrontier\n",
    "\n",
    "expected_returns = mean_historical_return(data.get(\"Close\"))\n",
    "cov_matrix = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "optimizer = EfficientFrontier(expected_returns, cov_matrix)\n",
    "weights = optimizer.max_sharpe()\n",
    "weights"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f718768-cf18-44fc-af63-ac18b28312c7",
   "metadata": {},
   "source": [
    "#### Parsing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1d01cc1c-99f3-47b6-91ef-5781d5ce2de0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectorbtpro.portfolio.pfopt.base import resolve_pypfopt_func_kwargs\n",
    "\n",
    "print(vbt.format_func(mean_historical_return))\n",
    "\n",
    "print(vbt.prettify(resolve_pypfopt_func_kwargs(\n",
    "    mean_historical_return, \n",
    "    prices=data.get(\"Close\"),\n",
    "    freq=\"1h\",\n",
    "    year_freq=\"365d\",\n",
    "    other_arg=100\n",
    ")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42341016-70d0-4ba1-b386-97b0ca8a3422",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(vbt.prettify(resolve_pypfopt_func_kwargs(\n",
    "    EfficientFrontier, \n",
    "    prices=data.get(\"Close\")\n",
    ")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd9d9605-e5db-4466-af74-25a4f8fe7eae",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(vbt.prettify(resolve_pypfopt_func_kwargs(\n",
    "    EfficientFrontier, \n",
    "    prices=data.get(\"Close\"),\n",
    "    expected_returns=\"ema_historical_return\",\n",
    "    cov_matrix=\"sample_cov\"\n",
    ")))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24a6eef0-9743-4a77-9966-9f049ecf3bc1",
   "metadata": {},
   "source": [
    "#### Auto-optimization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c5a41b5-0c06-445e-8279-fab28bd64ee2",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(prices=data.get(\"Close\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "999ff2a1-57db-4f25-817f-296e3323b65e",
   "metadata": {},
   "outputs": [],
   "source": [
    "S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "ef = EfficientFrontier(None, S, weight_bounds=(-1, 1))\n",
    "ef.min_volatility()\n",
    "weights = ef.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45ed42f6-34e7-481a-b1bd-3b3d8e45ca9c",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    expected_returns=None,\n",
    "    weight_bounds=(-1, 1),\n",
    "    target=\"min_volatility\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6ed4f49-2c33-4a12-9c6b-f91dfa7d381b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypfopt.expected_returns import capm_return\n",
    "\n",
    "sector_mapper = {\n",
    "    \"ADAUSDT\": \"DeFi\",\n",
    "    \"BNBUSDT\": \"DeFi\",\n",
    "    \"BTCUSDT\": \"Payment\",\n",
    "    \"ETHUSDT\": \"DeFi\",\n",
    "    \"XRPUSDT\": \"Payment\"\n",
    "}\n",
    "sector_lower = {\n",
    "    \"DeFi\": 0.75\n",
    "}\n",
    "sector_upper = {}\n",
    "\n",
    "mu = capm_return(data.get(\"Close\"))\n",
    "S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "ef = EfficientFrontier(mu, S)\n",
    "ef.add_sector_constraints(sector_mapper, sector_lower, sector_upper)\n",
    "adausdt_index = ef.tickers.index(\"ADAUSDT\")\n",
    "ef.add_constraint(lambda w: w[adausdt_index] == 0.10)\n",
    "ef.max_sharpe()\n",
    "weights = ef.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0920cb2-fcec-4d60-bbd1-860ff7e9f3e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "adausdt_index = list(sector_mapper.keys()).index(\"ADAUSDT\")\n",
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    sector_mapper=sector_mapper,\n",
    "    sector_lower=sector_lower,\n",
    "    sector_upper=sector_upper,\n",
    "    constraints=[lambda w: w[adausdt_index] == 0.10],\n",
    "    expected_returns=\"capm_return\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3baebc8f-b29e-4b99-924d-be1e30c41819",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypfopt.objective_functions import L2_reg\n",
    "\n",
    "mu = capm_return(data.get(\"Close\"))\n",
    "S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "ef = EfficientFrontier(mu, S)\n",
    "ef.add_sector_constraints(sector_mapper, sector_lower, sector_upper)\n",
    "ef.add_objective(L2_reg, gamma=0.1)  # gamme is the tuning parameter\n",
    "ef.efficient_risk(0.15)\n",
    "weights = ef.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "962886c1-70ed-4537-bce5-4329f47eeb66",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    expected_returns=\"capm_return\",\n",
    "    sector_mapper=sector_mapper,\n",
    "    sector_lower=sector_lower,\n",
    "    sector_upper=sector_upper,\n",
    "    objectives=[\"L2_reg\"],\n",
    "    gamma=0.1,\n",
    "    target=\"efficient_risk\",\n",
    "    target_volatility=0.15\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d0be75d-a98c-476d-8355-d4d9682ca44c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypfopt import EfficientSemivariance\n",
    "from pypfopt.expected_returns import returns_from_prices\n",
    "\n",
    "mu = capm_return(data.get(\"Close\"))\n",
    "returns = returns_from_prices(data.get(\"Close\"))\n",
    "returns = returns.dropna()\n",
    "es = EfficientSemivariance(mu, returns)\n",
    "es.efficient_return(0.01)\n",
    "weights = es.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ec40502-5d85-477f-a719-1d81626b308f",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    expected_returns=\"capm_return\",\n",
    "    optimizer=\"efficient_semivariance\",\n",
    "    target=\"efficient_return\",\n",
    "    target_return=0.01\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc19d54f-e653-46b7-9dc1-e9164a6cafb6",
   "metadata": {},
   "outputs": [],
   "source": [
    "initial_weights = np.array([1 / len(data.symbols)] * len(data.symbols))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1ca5f3d-717b-48de-8ca6-7365785314ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypfopt.objective_functions import transaction_cost\n",
    "\n",
    "mu = mean_historical_return(data.get(\"Close\"))\n",
    "S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "ef = EfficientFrontier(mu, S)\n",
    "ef.add_objective(transaction_cost, w_prev=initial_weights, k=0.001)\n",
    "ef.add_objective(L2_reg, gamma=0.05)\n",
    "ef.min_volatility()\n",
    "weights = ef.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2872ca4e-3e6c-4013-9f9e-6a78210da576",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    objectives=[\"transaction_cost\", \"L2_reg\"],\n",
    "    w_prev=initial_weights, \n",
    "    k=0.001,\n",
    "    gamma=0.05,\n",
    "    target=\"min_volatility\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "93a2b1f9-3b76-4b73-8875-2f8148aed53e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cvxpy as cp\n",
    "\n",
    "def logarithmic_barrier_objective(w, cov_matrix, k=0.1):\n",
    "    log_sum = cp.sum(cp.log(w))\n",
    "    var = cp.quad_form(w, cov_matrix)\n",
    "    return var - k * log_sum"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "618445c6-b151-4e48-ab97-09e73ea454ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "mu = mean_historical_return(data.get(\"Close\"))\n",
    "S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "ef = EfficientFrontier(mu, S, weight_bounds=(0.01, 0.3))\n",
    "ef.convex_objective(logarithmic_barrier_objective, cov_matrix=S, k=0.001)\n",
    "weights = ef.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "363b4772-b8f7-4780-8dd6-0879ca0be1e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    weight_bounds=(0.01, 0.3),\n",
    "    k=0.001,\n",
    "    target=logarithmic_barrier_objective\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d315ee2a-a300-4b2f-aabf-8dfd8e8a71ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "def deviation_risk_parity(w, cov_matrix):\n",
    "    cov_matrix = np.asarray(cov_matrix)\n",
    "    n = cov_matrix.shape[0]\n",
    "    rp = (w * (cov_matrix @ w)) / cp.quad_form(w, cov_matrix)\n",
    "    return cp.sum_squares(rp - 1 / n).value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "becea885-c1a8-4346-aa21-49e426fd6a30",
   "metadata": {},
   "outputs": [],
   "source": [
    "mu = mean_historical_return(data.get(\"Close\"))\n",
    "S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "ef = EfficientFrontier(mu, S)\n",
    "ef.nonconvex_objective(deviation_risk_parity, ef.cov_matrix)\n",
    "weights = ef.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d23a8f29-5fcb-489f-a566-d5e303009009",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    target=deviation_risk_parity,\n",
    "    target_is_convex=False\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52331cdc-4078-4d33-b6a5-c65f85afc1c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "sp500_data = vbt.YFData.pull(\n",
    "    \"^GSPC\", \n",
    "    start=data.wrapper.index[0], \n",
    "    end=data.wrapper.index[-1]\n",
    ")\n",
    "market_caps = data.get(\"Close\") * data.get(\"Volume\")\n",
    "viewdict = {\n",
    "    \"ADAUSDT\": 0.20, \n",
    "    \"BNBUSDT\": -0.30, \n",
    "    \"BTCUSDT\": 0, \n",
    "    \"ETHUSDT\": -0.2, \n",
    "    \"XRPUSDT\": 0.15\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "953df670-d6bc-4530-a5a7-4e6120fc64d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypfopt.black_litterman import (\n",
    "    market_implied_risk_aversion,\n",
    "    market_implied_prior_returns,\n",
    "    BlackLittermanModel\n",
    ")\n",
    "\n",
    "S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
    "delta = market_implied_risk_aversion(sp500_data.get(\"Close\"))\n",
    "prior = market_implied_prior_returns(market_caps.iloc[-1], delta, S)\n",
    "bl = BlackLittermanModel(S, pi=prior, absolute_views=viewdict)\n",
    "rets = bl.bl_returns()\n",
    "ef = EfficientFrontier(rets, S)\n",
    "ef.min_volatility()\n",
    "weights = ef.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48480f30-3641-463f-8a8e-5ce0485d1795",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    expected_returns=\"bl_returns\",\n",
    "    market_prices=sp500_data.get(\"Close\"),\n",
    "    market_caps=market_caps.iloc[-1],\n",
    "    absolute_views=viewdict,\n",
    "    target=\"min_volatility\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "597a8769-bf4c-49ee-8af6-e15db567d9e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypfopt import HRPOpt\n",
    "\n",
    "rets = returns_from_prices(data.get(\"Close\"))\n",
    "hrp = HRPOpt(rets)\n",
    "hrp.optimize()\n",
    "weights = hrp.clean_weights()\n",
    "weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "04648737-d27f-4c57-96a7-7a898b7bbf30",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(\n",
    "    prices=data.get(\"Close\"),\n",
    "    optimizer=\"hrp\",\n",
    "    target=\"optimize\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ed59e17-aff0-4d23-b896-bce0a4d45fef",
   "metadata": {},
   "source": [
    "#### Argument groups"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ddbad7dd-8655-40fa-8b0a-d85deb808cec",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.pypfopt_optimize(  \n",
    "    prices=data.get(\"Close\"),\n",
    "    expected_returns=\"bl_returns\",  \n",
    "    market_prices=sp500_data.get(\"Close\"),\n",
    "    market_caps=market_caps.iloc[-1],\n",
    "    absolute_views=viewdict,\n",
    "    target=\"min_volatility\",\n",
    "    cov_matrix=vbt.pfopt_func_dict({\n",
    "        \"EfficientFrontier\": \"sample_cov\",\n",
    "        \"_def\": \"ledoit_wolf\"\n",
    "    })\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5afd87a6-eaa1-4656-9e09-f988f811c139",
   "metadata": {},
   "source": [
    "#### Periodically"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "69629e9e-dcff-48a0-9af6-6ccef81d1dce",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_pypfopt(\n",
    "    prices=data.get(\"Close\"),\n",
    "    every=\"W\"\n",
    ")\n",
    "\n",
    "pfo.plot().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "719a764c-a500-41ad-b93b-2161b618373b",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_pypfopt(\n",
    "    prices=data.get(\"Close\"),\n",
    "    every=\"W\",\n",
    "    target=vbt.Param([\n",
    "        \"max_sharpe\", \n",
    "        \"min_volatility\", \n",
    "        \"max_quadratic_utility\"\n",
    "    ])\n",
    ")\n",
    "\n",
    "pfo.plot(column=\"min_volatility\").show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91d13211-9be7-4bd6-9357-23fa3320ffa4",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = pfo.simulate(data, freq=\"1h\")\n",
    "\n",
    "pf.sharpe_ratio"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1eb1e34-c040-49ab-96c5-2fe596444b50",
   "metadata": {},
   "source": [
    "### Riskfolio-Lib"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2177d3d7-963e-4e88-a20d-ff649699ff00",
   "metadata": {},
   "source": [
    "#### Parsing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bcbe3a9a-1473-4571-8a8f-ca32815ede4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "import riskfolio as rp\n",
    "\n",
    "returns = data.get(\"Close\").vbt.to_returns()\n",
    "\n",
    "port = rp.Portfolio(returns=returns)\n",
    "port.assets_stats(\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\",\n",
    "    d=0.94\n",
    ")\n",
    "w = port.optimization(\n",
    "    model=\"Classic\",\n",
    "    rm=\"MV\",\n",
    "    obj=\"Sharpe\",\n",
    "    rf=0,\n",
    "    l=0,\n",
    "    hist=True\n",
    ")\n",
    "print(w.T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d0c602c-0ec9-45ac-b663-9cff902154bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectorbtpro.utils.parsing import get_func_arg_names\n",
    "\n",
    "get_func_arg_names(port.assets_stats)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44b61c1c-d5b8-4fed-a994-df2cb018c820",
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectorbtpro.portfolio.pfopt.base import resolve_riskfolio_func_kwargs\n",
    "\n",
    "resolve_riskfolio_func_kwargs(\n",
    "    port.assets_stats,\n",
    "    method_mu=\"hist\",\n",
    "    method_cov=\"hist\",\n",
    "    model=\"Classic\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9b0e924-0a7e-4241-9fb1-cf4ae622c718",
   "metadata": {},
   "outputs": [],
   "source": [
    "resolve_riskfolio_func_kwargs(\n",
    "    port.assets_stats,\n",
    "    method_mu=\"hist\",\n",
    "    method_cov=\"hist\",\n",
    "    model=\"Classic\",\n",
    "    func_kwargs=dict(\n",
    "        assets_stats=dict(method_mu=\"ewma1\"),\n",
    "        optimization=dict(model=\"BL\")\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "acc762cc-a303-4a17-8056-d6d387ce7949",
   "metadata": {},
   "source": [
    "#### Auto-optimization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d77e4539-e19d-4496-9694-8fd67ee3f753",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(returns)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6a920e0-080a-4663-ad16-fd2e3de42e39",
   "metadata": {},
   "outputs": [],
   "source": [
    "port = rp.Portfolio(returns=returns)\n",
    "port.assets_stats(\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    d=0.94\n",
    ")\n",
    "w = port.optimization(\n",
    "    model=\"Classic\", \n",
    "    rm=\"UCI\", \n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    hist=True\n",
    ")\n",
    "print(w.T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0cc3ab5a-e304-4f44-9cc3-05f458d7818b",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(\n",
    "    returns,\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    d=0.94,\n",
    "    rm=\"UCI\", \n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    hist=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5cafb960-87ab-4aaf-818a-085270ade0db",
   "metadata": {},
   "outputs": [],
   "source": [
    "port = rp.Portfolio(returns=returns)\n",
    "port.assets_stats(\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    d=0.94\n",
    ")\n",
    "port.wc_stats(\n",
    "    box=\"s\", \n",
    "    ellip=\"s\", \n",
    "    q=0.05, \n",
    "    n_sim=3000, \n",
    "    window=3, \n",
    "    dmu=0.1, \n",
    "    dcov=0.1, \n",
    "    seed=0\n",
    ")\n",
    "w = port.wc_optimization(\n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    Umu=\"box\", \n",
    "    Ucov=\"box\"\n",
    ")\n",
    "print(w.T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "edf7699f-b286-4b00-b3a9-244641840cf1",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(\n",
    "    returns,\n",
    "    opt_method=\"wc\",\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    box=\"s\", \n",
    "    ellip=\"s\", \n",
    "    q=0.05, \n",
    "    n_sim=3000, \n",
    "    window=3, \n",
    "    dmu=0.1, \n",
    "    dcov=0.1, \n",
    "    seed=0,\n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    Umu=\"box\", \n",
    "    Ucov=\"box\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c175c0b-d47b-4039-9918-a4d496b941be",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(\n",
    "    returns,\n",
    "    func_kwargs=dict(\n",
    "        assets_stats=dict(\n",
    "            opt_method=\"wc\",\n",
    "            method_mu=\"hist\", \n",
    "            method_cov=\"hist\"\n",
    "        ),\n",
    "        wc_stats=dict(\n",
    "            box=\"s\", \n",
    "            ellip=\"s\", \n",
    "            q=0.05, \n",
    "            n_sim=3000, \n",
    "            window=3, \n",
    "            dmu=0.1, \n",
    "            dcov=0.1, \n",
    "            seed=0\n",
    "        ),\n",
    "        wc_optimization=dict(\n",
    "            obj=\"Sharpe\", \n",
    "            rf=0, \n",
    "            l=0, \n",
    "            Umu=\"box\", \n",
    "            Ucov=\"box\"\n",
    "        )\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "558d4ffc-9297-497a-bf76-0c5114696d00",
   "metadata": {},
   "outputs": [],
   "source": [
    "port = rp.Portfolio(returns=returns)\n",
    "port.sht = True # Allows to use Short Weights\n",
    "port.uppersht = 0.3 # Maximum value of sum of short weights in absolute value\n",
    "port.upperlng = 1.3 # Maximum value of sum of positive weights\n",
    "port.budget = 1.0 # No leverage\n",
    "port.assets_stats(\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    d=0.94\n",
    ")\n",
    "w = port.optimization(\n",
    "    model=\"Classic\", \n",
    "    rm=\"MV\", \n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    hist=True\n",
    ")\n",
    "print(w.T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0343022a-98e1-44e1-82c6-61c7d2175cfc",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(\n",
    "    returns,\n",
    "    sht=True,\n",
    "    uppersht=0.3,\n",
    "    upperlng=1.3,\n",
    "    budget=1.0,\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    d=0.94,\n",
    "    rm=\"MV\", \n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    hist=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "edd1993f-27de-4b36-9c2d-fb801f145e3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "port = rp.Portfolio(returns=returns)\n",
    "port.assets_stats(\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\",\n",
    "    d=0.94\n",
    ")\n",
    "asset_classes = {\"Assets\": returns.columns.tolist()}\n",
    "asset_classes = pd.DataFrame(asset_classes)\n",
    "constraints = {\n",
    "    \"Disabled\": [False, False],\n",
    "    \"Type\": [\"All Assets\", \"Assets\"],\n",
    "    \"Set\": [\"\", \"\"],\n",
    "    \"Position\": [\"\", \"BTCUSDT\"],\n",
    "    \"Sign\": [\">=\", \"<=\"],\n",
    "    'Weight': [0.1, 0.15],\n",
    "    \"Type Relative\": [\"\", \"\"],\n",
    "    \"Relative Set\": [\"\", \"\"],\n",
    "    \"Relative\": [\"\", \"\"],\n",
    "    \"Factor\": [\"\", \"\"],\n",
    "}\n",
    "constraints = pd.DataFrame(constraints)\n",
    "A, B = rp.assets_constraints(constraints, asset_classes)\n",
    "port.ainequality = A\n",
    "port.binequality = B\n",
    "w = port.optimization(\n",
    "    model=\"Classic\",\n",
    "    rm=\"MV\",\n",
    "    obj=\"Sharpe\",\n",
    "    rf=0,\n",
    "    l=0,\n",
    "    hist=True\n",
    ")\n",
    "print(w.T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d782230-ca6c-4816-b398-0a42510bf4f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(\n",
    "    returns,\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    constraints=[{\n",
    "        \"Type\": \"All Assets\",\n",
    "        \"Sign\": \">=\",\n",
    "        \"Weight\": 0.1\n",
    "    }, {\n",
    "        \"Type\": \"Assets\",\n",
    "        \"Position\": \"BTCUSDT\",\n",
    "        \"Sign\": \"<=\",\n",
    "        \"Weight\": 0.15\n",
    "    }],\n",
    "    d=0.94,\n",
    "    rm=\"MV\", \n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    hist=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37b200ad-f197-4b83-ae03-2bb7ff1fc21b",
   "metadata": {},
   "outputs": [],
   "source": [
    "tags = [\n",
    "    \"Smart contracts\",\n",
    "    \"Smart contracts\",\n",
    "    \"Payments\",\n",
    "    \"Smart contracts\",\n",
    "    \"Payments\"\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7db1f74f-4fc2-455e-bd4b-e42f8e0f733b",
   "metadata": {},
   "outputs": [],
   "source": [
    "port = rp.Portfolio(returns=returns)\n",
    "port.assets_stats(\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\",\n",
    "    d=0.94\n",
    ")\n",
    "asset_classes = {\n",
    "    \"Assets\": returns.columns.tolist(),\n",
    "    \"Tags\": tags\n",
    "}\n",
    "asset_classes = pd.DataFrame(asset_classes)\n",
    "constraints = {\n",
    "    \"Disabled\": [False],\n",
    "    \"Type\": [\"Classes\"],\n",
    "    \"Set\": [\"Tags\"],\n",
    "    \"Position\": [\"Smart contracts\"],\n",
    "    \"Sign\": [\">=\"],\n",
    "    'Weight': [0.8],\n",
    "    \"Type Relative\": [\"\"],\n",
    "    \"Relative Set\": [\"\"],\n",
    "    \"Relative\": [\"\"],\n",
    "    \"Factor\": [\"\"],\n",
    "}\n",
    "constraints = pd.DataFrame(constraints)\n",
    "A, B = rp.assets_constraints(constraints, asset_classes)\n",
    "port.ainequality = A\n",
    "port.binequality = B\n",
    "w = port.optimization(\n",
    "    model=\"Classic\",\n",
    "    rm=\"MV\",\n",
    "    obj=\"Sharpe\",\n",
    "    rf=0,\n",
    "    l=0,\n",
    "    hist=True\n",
    ")\n",
    "print(w.T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7403aeb2-fae6-487d-ae11-f50071dd0e1a",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(\n",
    "    returns,\n",
    "    method_mu=\"hist\", \n",
    "    method_cov=\"hist\", \n",
    "    asset_classes={\"Tags\": tags},\n",
    "    constraints=[{\n",
    "        \"Type\": \"Classes\",\n",
    "        \"Set\": \"Tags\",\n",
    "        \"Position\": \"Smart contracts\",\n",
    "        \"Sign\": \">=\",\n",
    "        \"Weight\": 0.8\n",
    "    }],\n",
    "    d=0.94,\n",
    "    rm=\"MV\", \n",
    "    obj=\"Sharpe\", \n",
    "    rf=0, \n",
    "    l=0, \n",
    "    hist=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ee363b9-7241-4569-8c90-f1f0feb98644",
   "metadata": {},
   "outputs": [],
   "source": [
    "port = rp.HCPortfolio(returns=returns)\n",
    "w = port.optimization(\n",
    "    model=\"NCO\",\n",
    "    codependence=\"pearson\",\n",
    "    covariance=\"hist\",\n",
    "    obj=\"MinRisk\",\n",
    "    rm=\"MV\",\n",
    "    rf=0,\n",
    "    l=2,\n",
    "    linkage=\"ward\",\n",
    "    max_k=10,\n",
    "    leaf_order=True\n",
    ")\n",
    "print(w.T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02c18d54-6721-49d8-a7de-fb3bd44a2dd9",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.riskfolio_optimize(\n",
    "    returns,\n",
    "    port_cls=\"HCPortfolio\",\n",
    "    model=\"NCO\",\n",
    "    codependence=\"pearson\",\n",
    "    covariance=\"hist\",\n",
    "    obj=\"MinRisk\",\n",
    "    rm=\"MV\",\n",
    "    rf=0,\n",
    "    l=2,\n",
    "    linkage=\"ward\",\n",
    "    max_k=10,\n",
    "    leaf_order=True\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c329e926-3cea-4610-b214-0d01c2001a7f",
   "metadata": {},
   "source": [
    "#### Periodically"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25c31c61-5735-4312-b3a0-f0d1513bcd4d",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_riskfolio(\n",
    "    returns=returns,\n",
    "    every=\"W\"\n",
    ")\n",
    "\n",
    "pfo.plot().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a538f664-dabe-4820-ae5b-0984df69f81c",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer.from_riskfolio(\n",
    "    returns=returns,\n",
    "    constraints=[{\n",
    "        \"Type\": \"Assets\",\n",
    "        \"Position\": \"BTCUSDT\",\n",
    "        \"Sign\": \"<=\",\n",
    "        \"Weight\": vbt.Param([0.1, 0.2, 0.3], name=\"BTCUSDT_maxw\")\n",
    "    }],\n",
    "    every=\"W\",\n",
    "    param_search_kwargs=dict(incl_types=list)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0835ab3e-b051-4a26-aa2b-a69ca9000d18",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pfo.allocations.groupby(\"BTCUSDT_maxw\").max())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf1c570e-764f-4c9a-be82-fdf1d0e2ae51",
   "metadata": {},
   "source": [
    "### Universal portfolios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8dcf11ca-dc92-4637-aab4-cffc2aa7b8cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from universal import tools, algos\n",
    "\n",
    "with vbt.WarningsFiltered():\n",
    "    algo = algos.CRP()\n",
    "    algo_result = algo.run(data.get(\"Close\"))\n",
    "    \n",
    "print(algo_result.weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d73a4383-09cd-43a1-9306-9585cf05d3ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "with vbt.WarningsFiltered():\n",
    "    algo = algos.DynamicCRP(\n",
    "        n=30, \n",
    "        min_history=7, \n",
    "        metric='sharpe', \n",
    "        alpha=0.01\n",
    "    )\n",
    "    algo_result = algo.run(data.get(\"Close\").resample(\"D\").last())\n",
    "    down_weights = algo_result.weights\n",
    "    \n",
    "print(down_weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "933da56f-6797-4d05-91cb-f5071a262058",
   "metadata": {},
   "outputs": [],
   "source": [
    "weights = down_weights.vbt.realign(\n",
    "    data.wrapper.index,\n",
    "    freq=\"1h\",\n",
    "    source_rbound=True,\n",
    "    target_rbound=True,\n",
    "    ffill=False\n",
    ")\n",
    "print(weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d40174db-9aa1-4397-b76e-f3dfd1f5db5a",
   "metadata": {},
   "outputs": [],
   "source": [
    "with vbt.WarningsFiltered():\n",
    "    down_pfo = vbt.PortfolioOptimizer.from_universal_algo(\n",
    "        \"DynamicCRP\",\n",
    "        data.get(\"Close\").resample(\"D\").last(),\n",
    "        n=vbt.Param([7, 14, 30, 90]), \n",
    "        min_history=7, \n",
    "        metric='sharpe', \n",
    "        alpha=0.01\n",
    "    )\n",
    "\n",
    "down_pfo.plot(column=90).show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "248bb47a-28d1-47e5-8c25-a4807c7d6c8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "resampler = vbt.Resampler(\n",
    "    down_pfo.wrapper.index, \n",
    "    data.wrapper.index, \n",
    "    target_freq=\"1h\"\n",
    ")\n",
    "pfo = down_pfo.resample(resampler)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "251b6943-4f95-4de9-914c-f3b5c3d326d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = pfo.simulate(data, freq=\"1h\")\n",
    "\n",
    "pf.sharpe_ratio"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "447827ee-687d-4b7b-ae6b-c4b7e2900b22",
   "metadata": {},
   "source": [
    "#### Custom algorithm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e4aa1ef-0306-4d62-8ec8-24f02a72509e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from universal.algo import Algo\n",
    "\n",
    "class MeanReversion(Algo):\n",
    "    PRICE_TYPE = 'log'\n",
    "    \n",
    "    def __init__(self, n):\n",
    "        self.n = n\n",
    "        super().__init__(min_history=n)\n",
    "    \n",
    "    def init_weights(self, cols):\n",
    "        return pd.Series(np.zeros(len(cols)), cols)\n",
    "    \n",
    "    def step(self, x, last_b, history):\n",
    "        ma = history.iloc[-self.n:].mean()\n",
    "        delta = x - ma\n",
    "        w = np.maximum(-delta, 0.)\n",
    "        return w / sum(w)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32919021-27fb-4121-80c5-640bab1811f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "with vbt.WarningsFiltered():\n",
    "    pfo = vbt.PortfolioOptimizer.from_universal_algo(\n",
    "        MeanReversion,\n",
    "        data.get(\"Close\").resample(\"D\").last(),\n",
    "        n=30,\n",
    "        every=\"W\"\n",
    "    )\n",
    "\n",
    "pfo.plot().show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5a0e81b-4b60-49d2-8a97-f61f3f937a76",
   "metadata": {},
   "source": [
    "## Dynamic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6939f3bf-7430-4e13-8194-dc687fc17f95",
   "metadata": {},
   "outputs": [],
   "source": [
    "GroupMemory = namedtuple(\"GroupMemory\", [\n",
    "    \"target_alloc\", \n",
    "    \"size_type\",\n",
    "    \"direction\",\n",
    "    \"order_value_out\"\n",
    "])\n",
    "\n",
    "@njit\n",
    "def pre_group_func_nb(c):\n",
    "    group_memory = GroupMemory(\n",
    "        target_alloc=np.full(c.group_len, np.nan),\n",
    "        size_type=np.full(c.group_len, vbt.pf_enums.SizeType.TargetPercent),\n",
    "        direction=np.full(c.group_len, vbt.pf_enums.Direction.Both),\n",
    "        order_value_out=np.full(c.group_len, np.nan)\n",
    "    )\n",
    "    return group_memory,\n",
    "\n",
    "@njit\n",
    "def pre_segment_func_nb(\n",
    "    c, \n",
    "    group_memory,\n",
    "    min_history,\n",
    "    threshold,\n",
    "    allocate_func_nb,\n",
    "    *args\n",
    "):\n",
    "    should_rebalance = False\n",
    "    \n",
    "    if c.i >= min_history:\n",
    "        in_position = False\n",
    "        for col in range(c.from_col, c.to_col):\n",
    "            if c.last_position[col] != 0:\n",
    "                in_position = True\n",
    "                break\n",
    "                \n",
    "        if not in_position:\n",
    "            should_rebalance = True\n",
    "        else:\n",
    "            curr_value = c.last_value[c.group]\n",
    "            for group_col in range(c.group_len):\n",
    "                col = c.from_col + group_col\n",
    "                curr_position = c.last_position[col]\n",
    "                curr_price = c.last_val_price[col]\n",
    "                curr_alloc = curr_position * curr_price / curr_value\n",
    "                curr_threshold = vbt.pf_nb.select_from_col_nb(c, col, threshold)\n",
    "                alloc_diff = curr_alloc - group_memory.target_alloc[group_col]\n",
    "                \n",
    "                if abs(alloc_diff) >= curr_threshold:\n",
    "                    should_rebalance = True\n",
    "                    break\n",
    "                    \n",
    "    if should_rebalance:\n",
    "        allocate_func_nb(c, group_memory, *args)\n",
    "        vbt.pf_nb.sort_call_seq_1d_nb(\n",
    "            c, \n",
    "            group_memory.target_alloc, \n",
    "            group_memory.size_type, \n",
    "            group_memory.direction, \n",
    "            group_memory.order_value_out\n",
    "        )\n",
    "        \n",
    "    return group_memory, should_rebalance\n",
    "\n",
    "@njit\n",
    "def order_func_nb(\n",
    "    c, \n",
    "    group_memory,\n",
    "    should_rebalance, \n",
    "    price,\n",
    "    fees\n",
    "):\n",
    "    if not should_rebalance:\n",
    "        return vbt.pf_nb.order_nothing_nb()\n",
    "    \n",
    "    group_col = c.col - c.from_col\n",
    "    return vbt.pf_nb.order_nb(\n",
    "        size=group_memory.target_alloc[group_col], \n",
    "        price=vbt.pf_nb.select_nb(c, price),\n",
    "        size_type=group_memory.size_type[group_col],\n",
    "        direction=group_memory.direction[group_col],\n",
    "        fees=vbt.pf_nb.select_nb(c, fees)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e55a608f-9ea4-46b3-bde3-188f20728530",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def uniform_allocate_func_nb(c, group_memory):\n",
    "    for group_col in range(c.group_len):\n",
    "        group_memory.target_alloc[group_col] = 1 / c.group_len"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c90956d-24e0-4d42-93ff-19a3c3688288",
   "metadata": {},
   "outputs": [],
   "source": [
    "def simulate_threshold_rebalancing(threshold, allocate_func_nb, *args, **kwargs):\n",
    "    return vbt.Portfolio.from_order_func(\n",
    "        data.get(\"Close\"),\n",
    "        open=data.get(\"Open\"),\n",
    "        pre_group_func_nb=pre_group_func_nb, \n",
    "        pre_group_args=(),\n",
    "        pre_segment_func_nb=pre_segment_func_nb, \n",
    "        pre_segment_args=(\n",
    "            0,\n",
    "            vbt.Rep(\"threshold\"),\n",
    "            allocate_func_nb,\n",
    "            *args\n",
    "        ),\n",
    "        order_func_nb=order_func_nb, \n",
    "        order_args=(vbt.Rep('price'), vbt.Rep('fees')),\n",
    "        broadcast_named_args=dict(\n",
    "            price=data.get(\"Close\"),\n",
    "            fees=0.005,\n",
    "            threshold=threshold\n",
    "        ),\n",
    "        cash_sharing=True,\n",
    "        group_by=vbt.ExceptLevel(\"symbol\"),\n",
    "        freq='1h', \n",
    "        **kwargs\n",
    "    )\n",
    "\n",
    "pf = simulate_threshold_rebalancing(0.05, uniform_allocate_func_nb)\n",
    "pf.plot_allocations().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ccbf4e6-b3b6-4b9d-b036-03483440d176",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = simulate_threshold_rebalancing(\n",
    "    vbt.Param(np.arange(1, 16) / 100, name=\"threshold\"), \n",
    "    uniform_allocate_func_nb\n",
    ")\n",
    "\n",
    "pf.sharpe_ratio"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0559a9a-d45a-4760-9d8d-4ee2cb8a3449",
   "metadata": {},
   "source": [
    "### Post-analysis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "588f1e8b-5c2c-41b2-b50a-1d6b5d912963",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def track_uniform_allocate_func_nb(c, group_memory, index_points, alloc_counter):\n",
    "    for group_col in range(c.group_len):\n",
    "        group_memory.target_alloc[group_col] = 1 / c.group_len\n",
    "    index_points[alloc_counter[0]] = c.i\n",
    "    alloc_counter[0] += 1\n",
    "\n",
    "index_points = np.empty(data.wrapper.shape[0], dtype=np.int_)\n",
    "alloc_counter = np.full(1, 0)\n",
    "pf = simulate_threshold_rebalancing(\n",
    "    0.05,\n",
    "    track_uniform_allocate_func_nb, \n",
    "    index_points, \n",
    "    alloc_counter\n",
    ")\n",
    "index_points = index_points[:alloc_counter[0]]\n",
    "\n",
    "data.wrapper.index[index_points]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2794df2a-d6b5-4b69-bfe4-765a06fe6200",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def random_allocate_func_nb(\n",
    "    c, \n",
    "    group_memory, \n",
    "    alloc_points, \n",
    "    alloc_weights, \n",
    "    alloc_counter\n",
    "):\n",
    "    weights = np.random.uniform(0, 1, c.group_len)\n",
    "    group_memory.target_alloc[:] = weights / weights.sum()\n",
    "    \n",
    "    group_count = alloc_counter[c.group]\n",
    "    count = alloc_counter.sum()\n",
    "    alloc_points[\"id\"][count] = group_count\n",
    "    alloc_points[\"col\"][count] = c.group\n",
    "    alloc_points[\"alloc_idx\"][count] = c.i\n",
    "    alloc_weights[count] = group_memory.target_alloc\n",
    "    alloc_counter[c.group] += 1\n",
    "\n",
    "thresholds = pd.Index(np.arange(1, 16) / 100, name=\"threshold\")\n",
    "max_entries = data.wrapper.shape[0] * len(thresholds)\n",
    "alloc_points = np.empty(max_entries, dtype=vbt.pf_enums.alloc_point_dt)\n",
    "alloc_weights = np.empty((max_entries, len(data.symbols)), dtype=np.float_)\n",
    "alloc_counter = np.full(len(thresholds), 0)\n",
    "\n",
    "pf = simulate_threshold_rebalancing(\n",
    "    vbt.Param(thresholds),\n",
    "    random_allocate_func_nb, \n",
    "    alloc_points, \n",
    "    alloc_weights,\n",
    "    alloc_counter,\n",
    "    seed=42\n",
    ")\n",
    "alloc_points = alloc_points[:alloc_counter.sum()]\n",
    "alloc_weights = alloc_weights[:alloc_counter.sum()]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d940d355-bce3-4443-857f-4ab62ed7aeb9",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def random_allocate_func_nb(c, group_memory):\n",
    "    weights = np.random.uniform(0, 1, c.group_len)\n",
    "    group_memory.target_alloc[:] = weights / weights.sum()\n",
    "    \n",
    "    group_count = c.in_outputs.alloc_counter[c.group]\n",
    "    count = c.in_outputs.alloc_counter.sum()\n",
    "    c.in_outputs.alloc_points[\"id\"][count] = group_count\n",
    "    c.in_outputs.alloc_points[\"col\"][count] = c.group\n",
    "    c.in_outputs.alloc_points[\"alloc_idx\"][count] = c.i\n",
    "    c.in_outputs.alloc_weights[count] = group_memory.target_alloc\n",
    "    c.in_outputs.alloc_counter[c.group] += 1\n",
    "\n",
    "alloc_points = vbt.RepEval(\"\"\"\n",
    "    max_entries = target_shape[0] * len(group_lens)\n",
    "    np.empty(max_entries, dtype=alloc_point_dt)\n",
    "\"\"\", context=dict(alloc_point_dt=vbt.pf_enums.alloc_point_dt))\n",
    "alloc_weights = vbt.RepEval(\"\"\"\n",
    "    max_entries = target_shape[0] * len(group_lens)\n",
    "    np.empty((max_entries, n_cols), dtype=np.float_)\n",
    "\"\"\", context=dict(n_cols=len(data.symbols)))\n",
    "alloc_counter = vbt.RepEval(\"np.full(len(group_lens), 0)\")\n",
    "\n",
    "InOutputs = namedtuple(\"InOutputs\", [\n",
    "    \"alloc_points\",\n",
    "    \"alloc_weights\",\n",
    "    \"alloc_counter\"\n",
    "])\n",
    "in_outputs = InOutputs(\n",
    "    alloc_points=alloc_points, \n",
    "    alloc_weights=alloc_weights,\n",
    "    alloc_counter=alloc_counter,\n",
    ")\n",
    "\n",
    "pf = simulate_threshold_rebalancing(\n",
    "    vbt.Param(np.arange(1, 16) / 100, name=\"threshold\"),\n",
    "    random_allocate_func_nb, \n",
    "    in_outputs=in_outputs,\n",
    "    seed=42\n",
    ")\n",
    "alloc_points = pf.in_outputs.alloc_points[:pf.in_outputs.alloc_counter.sum()]\n",
    "alloc_weights = pf.in_outputs.alloc_weights[:pf.in_outputs.alloc_counter.sum()]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1420356-030b-4815-a522-14ed1fe71642",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo = vbt.PortfolioOptimizer(\n",
    "    wrapper=pf.wrapper,\n",
    "    alloc_records=vbt.AllocPoints(\n",
    "        pf.wrapper.resolve(), \n",
    "        alloc_points\n",
    "    ),\n",
    "    allocations=alloc_weights\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fdf238e-e38b-4d6c-b36c-33ed342adccb",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pfo[0.1].allocations.describe())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "515f6baa-7b46-4f19-b84a-c9660ae95362",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.plot(column=0.1).show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7466d4f5-c1c9-416e-a47d-802b3f6813ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "pfo.plot(column=0.03).show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f8671183-8dc7-4a33-8d0a-559ac876f0bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf[0.03].plot_allocations().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "291d453b-1a17-436b-afbe-d3a8dfd8295c",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf.sharpe_ratio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b09c8a6-1f02-4380-b972-f4fe4660a261",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf_new = vbt.Portfolio.from_optimizer(\n",
    "    data,\n",
    "    pfo,  \n",
    "    val_price=data.get(\"Open\"), \n",
    "    freq=\"1h\", \n",
    "    fees=0.005\n",
    ")\n",
    "\n",
    "pf_new.sharpe_ratio"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4aabcbe-8baa-4507-a38a-a4b937d6a5c7",
   "metadata": {},
   "source": [
    "### Bonus 1: Own optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de4de790-9fd4-4030-8dfb-dae986de9012",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit(nogil=True)\n",
    "def optimize_portfolio_nb(\n",
    "    close, \n",
    "    val_price,\n",
    "    range_starts,\n",
    "    range_ends,\n",
    "    optimize_func_nb,\n",
    "    optimize_args=(),\n",
    "    price=np.inf,\n",
    "    fees=0.,\n",
    "    init_cash=100.,\n",
    "    group=0\n",
    "):\n",
    "    val_price_ = vbt.to_2d_array_nb(np.asarray(val_price))\n",
    "    price_ = vbt.to_2d_array_nb(np.asarray(price))\n",
    "    fees_ = vbt.to_2d_array_nb(np.asarray(fees))\n",
    "\n",
    "    order_records = np.empty(close.shape, dtype=vbt.pf_enums.order_dt)\n",
    "    order_counts = np.full(close.shape[1], 0, dtype=np.int_)\n",
    "    \n",
    "    order_value = np.empty(close.shape[1], dtype=np.float_)\n",
    "    call_seq = np.empty(close.shape[1], dtype=np.int_)\n",
    "    \n",
    "    last_position = np.full(close.shape[1], 0.0, dtype=np.float_)\n",
    "    last_debt = np.full(close.shape[1], 0.0, dtype=np.float_)\n",
    "    last_locked_cash = np.full(close.shape[1], 0.0, dtype=np.float_)\n",
    "    cash_now = float(init_cash)\n",
    "    free_cash_now = float(init_cash)\n",
    "    value_now = float(init_cash)\n",
    "\n",
    "    for k in range(len(range_starts)):\n",
    "        i = range_ends[k]\n",
    "        size = optimize_func_nb(\n",
    "            range_starts[k], \n",
    "            range_ends[k], \n",
    "            *optimize_args\n",
    "        )\n",
    "        \n",
    "        value_now = cash_now\n",
    "        for col in range(close.shape[1]):\n",
    "            val_price_now = vbt.flex_select_nb(val_price_, i, col)\n",
    "            value_now += last_position[col] * val_price_now\n",
    "        \n",
    "        for col in range(close.shape[1]):\n",
    "            val_price_now = vbt.flex_select_nb(val_price_, i, col)\n",
    "            exec_state = vbt.pf_enums.ExecState(\n",
    "                cash=cash_now,\n",
    "                position=last_position[col],\n",
    "                debt=last_debt[col],\n",
    "                locked_cash=last_locked_cash[col],\n",
    "                free_cash=free_cash_now,\n",
    "                val_price=val_price_now,\n",
    "                value=value_now,\n",
    "            )\n",
    "            order_value[col] = vbt.pf_nb.approx_order_value_nb(\n",
    "                exec_state,\n",
    "                size[col],\n",
    "                vbt.pf_enums.SizeType.TargetPercent,\n",
    "                vbt.pf_enums.Direction.Both,\n",
    "            )\n",
    "            call_seq[col] = col\n",
    "\n",
    "        vbt.pf_nb.insert_argsort_nb(order_value, call_seq)\n",
    "\n",
    "        for c in range(close.shape[1]):\n",
    "            col = call_seq[c]\n",
    "            \n",
    "            order = vbt.pf_nb.order_nb(\n",
    "                size=size[col],\n",
    "                price=vbt.flex_select_nb(price_, i, col),\n",
    "                size_type=vbt.pf_enums.SizeType.TargetPercent,\n",
    "                direction=vbt.pf_enums.Direction.Both,\n",
    "                fees=vbt.flex_select_nb(fees_, i, col),\n",
    "            )\n",
    "\n",
    "            price_area = vbt.pf_enums.PriceArea(\n",
    "                open=np.nan,\n",
    "                high=np.nan,\n",
    "                low=np.nan,\n",
    "                close=vbt.flex_select_nb(close, i, col),\n",
    "            )\n",
    "            val_price_now = vbt.flex_select_nb(val_price_, i, col)\n",
    "            exec_state = vbt.pf_enums.ExecState(\n",
    "                cash=cash_now,\n",
    "                position=last_position[col],\n",
    "                debt=last_debt[col],\n",
    "                locked_cash=last_locked_cash[col],\n",
    "                free_cash=free_cash_now,\n",
    "                val_price=val_price_now,\n",
    "                value=value_now,\n",
    "            )\n",
    "            _, new_exec_state = vbt.pf_nb.process_order_nb(\n",
    "                group=group,\n",
    "                col=col,\n",
    "                i=i,\n",
    "                exec_state=exec_state,\n",
    "                order=order,\n",
    "                price_area=price_area,\n",
    "                order_records=order_records,\n",
    "                order_counts=order_counts\n",
    "            )\n",
    "\n",
    "            cash_now = new_exec_state.cash\n",
    "            free_cash_now = new_exec_state.free_cash\n",
    "            value_now = new_exec_state.value\n",
    "            last_position[col] = new_exec_state.position\n",
    "            last_debt[col] = new_exec_state.debt\n",
    "            last_locked_cash[col] = new_exec_state.locked_cash\n",
    "\n",
    "    return vbt.nb.repartition_nb(order_records, order_counts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e345590b-3a5a-43ec-b325-3db9766d3ea1",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit(nogil=True)\n",
    "def sharpe_optimize_func_nb(\n",
    "    start_idx, \n",
    "    end_idx, \n",
    "    close, \n",
    "    num_tests, \n",
    "    ann_factor\n",
    "):\n",
    "    close_period = close[start_idx:end_idx]\n",
    "    returns = (close_period[1:] - close_period[:-1]) / close_period[:-1]\n",
    "    mean = vbt.nb.nanmean_nb(returns)\n",
    "    cov = np.cov(returns, rowvar=False)\n",
    "    best_sharpe_ratio = -np.inf\n",
    "    weights = np.full(close.shape[1], np.nan, dtype=np.float_)\n",
    "    \n",
    "    for i in range(num_tests):\n",
    "        w = np.random.random_sample(close.shape[1])\n",
    "        w = w / np.sum(w)\n",
    "        p_return = np.sum(mean * w) * ann_factor\n",
    "        p_std = np.sqrt(np.dot(w.T, np.dot(cov, w))) * np.sqrt(ann_factor)\n",
    "        sharpe_ratio = p_return / p_std\n",
    "        if sharpe_ratio > best_sharpe_ratio:\n",
    "            best_sharpe_ratio = sharpe_ratio\n",
    "            weights = w\n",
    "            \n",
    "    return weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0b16d9d-688a-49b5-8738-2191b1ad966a",
   "metadata": {},
   "outputs": [],
   "source": [
    "range_starts, range_ends = data.wrapper.get_index_ranges(every=\"W\")\n",
    "ann_factor = vbt.timedelta(\"365d\") / vbt.timedelta(\"1h\")\n",
    "init_cash = 100\n",
    "num_tests = 30\n",
    "fees = 0.005\n",
    "\n",
    "order_records = optimize_portfolio_nb(\n",
    "    data.get(\"Close\").values,\n",
    "    data.get(\"Open\").values,\n",
    "    range_starts,\n",
    "    range_ends,\n",
    "    sharpe_optimize_func_nb,\n",
    "    optimize_args=(data.get(\"Close\").values, num_tests, ann_factor),\n",
    "    fees=fees,\n",
    "    init_cash=init_cash\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7c2e668-d30b-4bbf-ab45-e7287eba39d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf = vbt.Portfolio(\n",
    "    wrapper=symbol_wrapper.regroup(True), \n",
    "    close=data.get(\"Close\"), \n",
    "    order_records=order_records, \n",
    "    log_records=np.array([]), \n",
    "    cash_sharing=True, \n",
    "    init_cash=init_cash\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aa378c28-5ec9-4999-88cf-edd11df53afa",
   "metadata": {},
   "outputs": [],
   "source": [
    "pf.plot_allocations().show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89431545-729d-4b7f-b465-afed7168ec53",
   "metadata": {},
   "source": [
    "### Bonus 2: Parameterization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ee3cc01-379c-4ee0-a188-78cb1fbdd526",
   "metadata": {},
   "outputs": [],
   "source": [
    "def merge_func(order_records_list, param_index):\n",
    "    sharpe_ratios = pd.Series(index=param_index, dtype=np.float_)\n",
    "    for i, order_records in enumerate(order_records_list):\n",
    "        pf = vbt.Portfolio(\n",
    "            wrapper=symbol_wrapper.regroup(True), \n",
    "            close=data.get(\"Close\"), \n",
    "            order_records=order_records, \n",
    "            cash_sharing=True, \n",
    "            init_cash=init_cash\n",
    "        )\n",
    "        sharpe_ratios.iloc[i] = pf.sharpe_ratio\n",
    "    return sharpe_ratios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ffd14c31-25fd-415c-9b2f-295e31732a1d",
   "metadata": {},
   "outputs": [],
   "source": [
    "param_optimize_portfolio_nb = vbt.parameterized(\n",
    "    optimize_portfolio_nb, \n",
    "    merge_func=merge_func,\n",
    "    merge_kwargs=dict(param_index=vbt.Rep(\"param_index\")),\n",
    "    engine=\"dask\",\n",
    "    chunk_len=4\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f1ec6941-a6df-462c-bd2d-043f1dee498b",
   "metadata": {},
   "outputs": [],
   "source": [
    "every_index = pd.Index([\"D\", \"W\", \"M\"], name=\"every\")\n",
    "num_tests_index = pd.Index([30, 50, 100], name=\"num_tests\")\n",
    "fees_index = pd.Index([0.0, 0.005, 0.01], name=\"fees\")\n",
    "\n",
    "range_starts = []\n",
    "range_ends = []\n",
    "for every in every_index:\n",
    "    index_ranges = symbol_wrapper.get_index_ranges(every=every)\n",
    "    range_starts.append(index_ranges[0])\n",
    "    range_ends.append(index_ranges[1])\n",
    "num_tests = num_tests_index.tolist()\n",
    "\n",
    "range_starts = vbt.Param(range_starts, level=0, keys=every_index)\n",
    "range_ends = vbt.Param(range_ends, level=0, keys=every_index)\n",
    "num_tests = vbt.Param(num_tests, level=1, keys=num_tests_index)\n",
    "fees = vbt.Param(fees_index.values, level=2, keys=fees_index)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5534b797-2c85-4c33-8143-d41b9542c1df",
   "metadata": {},
   "outputs": [],
   "source": [
    "sharpe_ratios = param_optimize_portfolio_nb(\n",
    "    data.get(\"Close\").values,\n",
    "    data.get(\"Open\").values,\n",
    "    range_starts,\n",
    "    range_ends,\n",
    "    sharpe_optimize_func_nb,\n",
    "    optimize_args=(\n",
    "        data.get(\"Close\").values, \n",
    "        num_tests, \n",
    "        ann_factor\n",
    "    ),\n",
    "    fees=fees,\n",
    "    init_cash=init_cash,\n",
    "    group=vbt.Rep(\"config_idx\")\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "508c1fc7-c141-4428-9844-6bef69ea64cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "sharpe_ratios"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d87c6506-c84f-49bc-9236-3804cd22dd22",
   "metadata": {},
   "source": [
    "### Bonus 3: Hyperopt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "111953f4-6b42-4023-b204-4b4480bf1ed8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def objective(kwargs):\n",
    "    close_values = data.get(\"Close\").values\n",
    "    open_values = data.get(\"Open\").values\n",
    "    index_ranges = symbol_wrapper.get_index_ranges(every=kwargs[\"every\"])\n",
    "    order_records = optimize_portfolio_nb(\n",
    "        close_values,\n",
    "        open_values,\n",
    "        index_ranges[0],\n",
    "        index_ranges[1],\n",
    "        sharpe_optimize_func_nb,\n",
    "        optimize_args=(close_values, kwargs[\"num_tests\"], ann_factor),\n",
    "        fees=vbt.to_2d_array(kwargs[\"fees\"]),\n",
    "        init_cash=init_cash\n",
    "    )\n",
    "    pf = vbt.Portfolio(\n",
    "        wrapper=symbol_wrapper.regroup(True), \n",
    "        close=data.get(\"Close\"), \n",
    "        order_records=order_records, \n",
    "        log_records=np.array([]), \n",
    "        cash_sharing=True, \n",
    "        init_cash=init_cash\n",
    "    )\n",
    "    return -pf.sharpe_ratio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7de2667c-977e-4595-808d-17afc7279796",
   "metadata": {},
   "outputs": [],
   "source": [
    "from hyperopt import fmin, tpe, hp\n",
    "\n",
    "space = {\n",
    "    \"every\": hp.choice(\"every\", [\"%dD\" % n for n in range(1, 100)]),\n",
    "    \"num_tests\": hp.quniform(\"num_tests\", 5, 100, 1),\n",
    "    \"fees\": hp.uniform('fees', 0, 0.05)\n",
    "}\n",
    "\n",
    "best = fmin(\n",
    "    fn=objective,\n",
    "    space=space,\n",
    "    algo=tpe.suggest,\n",
    "    max_evals=30\n",
    ")\n",
    "best"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a19b8f97-e160-4238-bafa-6701c5a7d3d7",
   "metadata": {},
   "source": [
    "### Bonus 4: Hybrid"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d0c221b-4c46-4875-ac11-248518e025f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def optimize_func(\n",
    "    data, \n",
    "    index_slice, \n",
    "    temp_allocations, \n",
    "    temp_pfs, \n",
    "    threshold\n",
    "):\n",
    "    sub_data = data.iloc[index_slice]\n",
    "    if len(temp_allocations) > 0:\n",
    "        prev_allocation = sub_data.symbol_wrapper.wrap(\n",
    "            [temp_allocations[-1]], \n",
    "            index=sub_data.wrapper.index[[0]]\n",
    "        )\n",
    "        prev_pfo = vbt.PortfolioOptimizer.from_allocations(\n",
    "            sub_data.symbol_wrapper,\n",
    "            prev_allocation\n",
    "        )\n",
    "        if len(temp_pfs) > 0:\n",
    "            init_cash = temp_pfs[-1].cash.iloc[-1]\n",
    "            init_position = temp_pfs[-1].assets.iloc[-1]\n",
    "            init_price = temp_pfs[-1].close.iloc[-1]\n",
    "        else:\n",
    "            init_cash = 100.\n",
    "            init_position = 0.\n",
    "            init_price = np.nan\n",
    "        prev_pf = prev_pfo.simulate(\n",
    "            sub_data,\n",
    "            init_cash=init_cash, \n",
    "            init_position=init_position,\n",
    "            init_price=init_price\n",
    "        )\n",
    "        temp_pfs.append(prev_pf)\n",
    "        should_rebalance = False\n",
    "        curr_alloc = prev_pf.allocations.iloc[-1].values\n",
    "        if (np.abs(curr_alloc - temp_allocations[-1]) >= threshold).any():\n",
    "            should_rebalance = True\n",
    "    else:\n",
    "        should_rebalance = True\n",
    "    n_symbols = len(sub_data.symbols)\n",
    "    if should_rebalance:\n",
    "        new_alloc = np.full(n_symbols, 1 / n_symbols)\n",
    "    else:\n",
    "        new_alloc = np.full(n_symbols, np.nan)\n",
    "    temp_allocations.append(new_alloc)\n",
    "    return new_alloc\n",
    "\n",
    "pfs = []\n",
    "allocations = []\n",
    "pfopt = vbt.PortfolioOptimizer.from_optimize_func(\n",
    "    data.symbol_wrapper,\n",
    "    optimize_func,\n",
    "    data,\n",
    "    vbt.Rep(\"index_slice\"),\n",
    "    allocations,\n",
    "    pfs,\n",
    "    0.03,\n",
    "    every=\"W\"\n",
    ")\n",
    "pf = pfopt.simulate(data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e1cc1c7c-e487-45a7-b36b-dbd9d6de9ee8",
   "metadata": {},
   "outputs": [],
   "source": [
    "final_values = pd.concat(map(lambda x: x.value[[-1]], pfs))\n",
    "final_values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae5aeee2-7b53-4c77-b848-abe9c23b3c69",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.testing.assert_series_equal(\n",
    "    final_values,\n",
    "    pf.value.loc[final_values.index],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ed00107-34bd-4cb8-abfd-6f5432a56f2c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "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.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
