{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "906f22ee-4fda-4b00-86b5-666eeba85526",
   "metadata": {},
   "source": [
    "# Signal development"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea0a17e8-dede-43b7-8d69-ae44fa9eeef0",
   "metadata": {},
   "source": [
    "## Generation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c92e74cd-bef9-43e1-a78e-f39d308798df",
   "metadata": {},
   "source": [
    "### Comparison"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bc046d2c-54c8-4363-aa50-9535db8c1fa7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectorbtpro import *\n",
    "# whats_imported()\n",
    "\n",
    "vbt.settings.set_theme(\"dark\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3a5fa071-d8c4-4443-8528-88c5cab650cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = vbt.BinanceData.pull(\n",
    "    [\"BTCUSDT\", \"ETHUSDT\"], \n",
    "    start=\"2021-01-01\",\n",
    "    end=\"2022-01-01\"\n",
    ")\n",
    "print(data.get(\"Low\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b43d9cc2-631e-44a4-9a0b-83aa08c3a722",
   "metadata": {},
   "outputs": [],
   "source": [
    "bb = vbt.talib(\"BBANDS\").run(\n",
    "    data.get(\"Close\"),\n",
    "    timeperiod=vbt.Default(14),\n",
    "    nbdevup=vbt.Default(2),\n",
    "    nbdevdn=vbt.Default(2)\n",
    ")\n",
    "print(bb.lowerband)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3ee3576-aa6f-4652-83de-fbaa47eb7137",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = data.get(\"Low\") < bb.lowerband\n",
    "print(mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73b16f6b-cd9d-4ccb-92b8-d374f59dfe4b",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f1032773-752b-435f-8a96-3aea5e7fec6e",
   "metadata": {},
   "outputs": [],
   "source": [
    "bb_mult = vbt.talib(\"BBANDS\").run(\n",
    "    data.get(\"Close\"),\n",
    "    timeperiod=vbt.Default(14),\n",
    "    nbdevup=[2, 3],\n",
    "    nbdevdn=[2, 3]\n",
    ")\n",
    "# mask = data.get(\"Low\") < bb_mult.lowerband"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6aafc576-9b8a-4475-ad4c-1aaa7f71d4b1",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = data.get(\"Low\").vbt < bb_mult.lowerband\n",
    "print(mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dbe069e6-47c0-4760-b6d7-ebc2e8bf9e62",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6db65b8-b971-433f-b4bc-fe97263baa30",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = bb_mult.lowerband_above(data.get(\"Low\"))\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7301f0a1-6e79-4da1-9daf-8345c35da38d",
   "metadata": {},
   "source": [
    "#### Thresholds"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2487f5f5-2277-4608-a6ee-bd26a88b3d48",
   "metadata": {},
   "outputs": [],
   "source": [
    "bandwidth = (bb.upperband - bb.lowerband) / bb.middleband\n",
    "\n",
    "mask = bandwidth.vbt > vbt.Param([0.15, 0.3], name=\"threshold\")\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f634615-3e6f-4ba8-a3e4-843748fb6663",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = bandwidth.vbt.combine(\n",
    "    [0.15, 0.3],\n",
    "    combine_func=np.greater, \n",
    "    keys=pd.Index([0.15, 0.3], name=\"threshold\")\n",
    ")\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6bda89d4-6381-4ec1-b1bc-ffe302454181",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = pd.concat(\n",
    "    (bandwidth > 0.15, bandwidth > 0.3), \n",
    "    keys=pd.Index([0.15, 0.3], name=\"threshold\"), \n",
    "    axis=1\n",
    ")\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66ef2746-1d4f-48bd-887e-da3ce86aa2d9",
   "metadata": {},
   "source": [
    "#### Crossovers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "112ffe71-78da-4bba-a9d4-7f5ffeb5cb3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "low_below_lband = data.get(\"Low\") < bb.lowerband\n",
    "mask = low_below_lband.vbt.signals.first()\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e92bbb10-724a-4f28-9a54-6d41f36f7bf4",
   "metadata": {},
   "outputs": [],
   "source": [
    "btc_low = data.get(\"Low\", \"BTCUSDT\").rename(\"Low\")\n",
    "btc_lowerband = bb.lowerband[\"BTCUSDT\"].rename(\"Lower Band\")\n",
    "btc_mask = mask[\"BTCUSDT\"].rename(\"Signals\")\n",
    "\n",
    "fig = btc_low.vbt.plot()\n",
    "btc_lowerband.vbt.plot(fig=fig)\n",
    "btc_mask.vbt.signals.plot_as_markers(\n",
    "    y=btc_low, \n",
    "    trace_kwargs=dict(\n",
    "        marker=dict(\n",
    "            color=\"#DFFF00\"\n",
    "        )\n",
    "    ),\n",
    "    fig=fig\n",
    ").show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9caf1ea8-18b8-42d6-b759-e3ef87ea23ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = low_below_lband.vbt.signals.first(after_false=True)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d22b9fb1-2757-4fe9-b257-233e8a6428b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_low = pd.Series([10, 9, 8, 9, 8])\n",
    "sample_lband = pd.Series([np.nan, np.nan, 9, 8, 9])\n",
    "sample_mask = sample_low < sample_lband\n",
    "sample_mask.vbt.signals.first(after_false=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd63d8f4-f71e-4b77-ba9e-f3aa5fab70d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_mask[sample_lband.ffill().isnull()] = True\n",
    "sample_mask.vbt.signals.first(after_false=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48ec8914-6279-4e04-9577-7a61123e8f9b",
   "metadata": {},
   "outputs": [],
   "source": [
    "buffer = sample_lband.ffill().isnull().sum(axis=0).max()\n",
    "buffer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e7b37d6-435b-4d05-b813-c71e3533d7f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_buf_mask = sample_low.iloc[buffer:] < sample_lband.iloc[buffer:]\n",
    "sample_buf_mask = sample_buf_mask.vbt.signals.first(after_false=True)\n",
    "sample_mask = sample_low.vbt.wrapper.fill(False)\n",
    "sample_mask.loc[sample_buf_mask.index] = sample_buf_mask\n",
    "sample_mask"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "497a1101-5e73-4bd3-9d6d-ceb51bd6ac58",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = data.get(\"Low\").vbt.crossed_below(bb.lowerband, wait=1)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e385bd31-4be5-40c8-95eb-23ec488dcff2",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = bb.lowerband_crossed_above(data.get(\"Low\"), wait=1)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67ac0af5-b8bc-4e4f-8351-e33661778165",
   "metadata": {},
   "source": [
    "### Logical operators"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "931d10e3-7259-49f7-bde3-3c6b11e356d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond1 = data.get(\"Low\") < bb.lowerband\n",
    "cond2 = bandwidth > 0.3\n",
    "cond3 = data.get(\"High\") > bb.upperband\n",
    "cond4 = bandwidth < 0.15\n",
    "\n",
    "mask = (cond1 & cond2) | (cond3 & cond4)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a8e5bb5-3a3f-4e95-8805-78dbeb73e119",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond1 = data.get(\"Low\").vbt < bb.lowerband\n",
    "cond2 = bandwidth.vbt > vbt.Param([0.3, 0.3, 0.4, 0.4], name=\"cond2_th\")\n",
    "cond3 = data.get(\"High\").vbt > bb.upperband\n",
    "cond4 = bandwidth.vbt < vbt.Param([0.1, 0.2, 0.1, 0.2], name=\"cond4_th\")\n",
    "\n",
    "mask = (cond1.vbt & cond2).vbt | (cond3.vbt & cond4)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96fec8a9-27fe-4ec0-9745-06b612a8beeb",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond1 = data.get(\"Low\").vbt < bb.lowerband\n",
    "cond2 = bandwidth.vbt > vbt.Param([0.3, 0.4], name=\"cond2_th\")\n",
    "cond3 = data.get(\"High\").vbt > bb.upperband\n",
    "cond4 = bandwidth.vbt < vbt.Param([0.1, 0.2], name=\"cond4_th\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "553a64f0-a358-4e98-a0f6-e05d44ba84be",
   "metadata": {},
   "outputs": [],
   "source": [
    "i1 = np.split(np.arange(len(cond1.columns)), len(cond1.columns) // 2)\n",
    "i2 = np.split(np.arange(len(cond2.columns)), len(cond2.columns) // 2)\n",
    "i3 = np.split(np.arange(len(cond3.columns)), len(cond3.columns) // 2)\n",
    "i4 = np.split(np.arange(len(cond4.columns)), len(cond4.columns) // 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55799254-e8c1-4b67-aba1-a7015ebe5b92",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(i1)\n",
    "print(i2)\n",
    "print(i3)\n",
    "print(i4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6cef9c96-52bf-42be-8c70-750c39bea02a",
   "metadata": {},
   "outputs": [],
   "source": [
    "i1, i2, i3, i4 = zip(*product(i1, i2, i3, i4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7348da68-f874-4334-88ac-be80db0ec4e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(i1)\n",
    "print(i2)\n",
    "print(i3)\n",
    "print(i4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f055cbef-62ef-4958-a6c2-9c253e023e9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "i1 = np.asarray(i1).flatten()\n",
    "i2 = np.asarray(i2).flatten()\n",
    "i3 = np.asarray(i3).flatten()\n",
    "i4 = np.asarray(i4).flatten()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f7941975-2907-4a19-811f-e98848822d66",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(i1)\n",
    "print(i2)\n",
    "print(i3)\n",
    "print(i4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ed4ad6f-3182-4da6-9bfa-8ecb705af57e",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond1 = cond1.iloc[:, i1]\n",
    "cond2 = cond2.iloc[:, i2]\n",
    "cond3 = cond3.iloc[:, i3]\n",
    "cond4 = cond4.iloc[:, i4]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38fb824d-8aa5-494d-b2f8-bea49e2d584e",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = (cond1.vbt & cond2).vbt | (cond3.vbt & cond4)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e5545a3-b347-41de-bee5-9954ff2b0698",
   "metadata": {},
   "outputs": [],
   "source": [
    "MaskGenerator = vbt.IF.from_expr(\"\"\"\n",
    "upperband, middleband, lowerband = @res_talib_bbands\n",
    "bandwidth = (upperband - lowerband) / middleband\n",
    "cond1 = low < lowerband\n",
    "cond2 = bandwidth > @p_cond2_th\n",
    "cond3 = high > upperband\n",
    "cond4 = bandwidth < @p_cond4_th\n",
    "@out_mask:(cond1 & cond2) | (cond3 & cond4)\n",
    "\"\"\")\n",
    "\n",
    "print(vbt.format_func(MaskGenerator.run, incl_doc=False))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3bbbab02-1e9e-497c-8616-cbfaa19cd39e",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask_generator = MaskGenerator.run(\n",
    "    high=data.get(\"High\"),\n",
    "    low=data.get(\"Low\"),\n",
    "    close=data.get(\"Close\"),\n",
    "    cond2_th=[0.3, 0.4],\n",
    "    cond4_th=[0.1, 0.2],\n",
    "    bbands_timeperiod=vbt.Default(14),\n",
    "    param_product=True\n",
    ")\n",
    "mask_generator.mask.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d6deb30-8e48-48e1-b06e-699454a28098",
   "metadata": {},
   "source": [
    "### Shifting"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58f200ec-96b8-450b-afc4-76307bf59549",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond1 = data.get(\"Low\") < bb.lowerband\n",
    "cond2 = bandwidth > bandwidth.shift(1)\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "efef3852-ba5a-4670-8ccf-cb51e5d41432",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond2 = bandwidth > bandwidth.rolling(\"7d\").apply(lambda x: x[0])\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b308a3e-f8e8-46b0-b016-2aa1933dc44d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def exactly_ago(sr):\n",
    "    if sr.index[0] == sr.index[-1] - vbt.timedelta(\"7d\"):\n",
    "        return sr.iloc[0]\n",
    "    return np.nan\n",
    "\n",
    "cond_7d_ago = bandwidth.rolling(\"8d\").apply(exactly_ago, raw=False)\n",
    "cond2 = bandwidth > cond_7d_ago\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a41f9fe9-51d8-420b-8352-2238cf58208f",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def exactly_ago_meta_nb(from_i, to_i, col, index, freq, arr):\n",
    "    if index[from_i] == index[to_i - 1] - freq:\n",
    "        return arr[from_i, col]\n",
    "    return np.nan\n",
    "\n",
    "cond_7d_ago = vbt.pd_acc.rolling_apply(\n",
    "    \"8d\",\n",
    "    exactly_ago_meta_nb,\n",
    "    bandwidth.index.values,\n",
    "    vbt.timedelta(\"7d\").to_timedelta64(),\n",
    "    vbt.to_2d_array(bandwidth),\n",
    "    wrapper=bandwidth.vbt.wrapper\n",
    ")\n",
    "cond2 = bandwidth > cond_7d_ago\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa58a149-9754-41da-ab53-e7642d0c3410",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond2 = bandwidth > bandwidth.vbt.ago(\"7d\")\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae73e445-ca38-4b64-acee-daed9f4ba6fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "bandwidth.iloc[-8]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bba2e968-9d6a-422a-a88f-abad435da438",
   "metadata": {},
   "outputs": [],
   "source": [
    "bandwidth.vbt.ago(\"7d\").iloc[-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50937a93-0f50-47c2-a28f-578baa33609a",
   "metadata": {},
   "source": [
    "### Truth value testing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f1e5ab80-5cda-46cf-8a30-d20ab4ab9aab",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond2 = data.get(\"Close\").vbt.crossed_below(bb.middleband)\n",
    "cond2 = cond2.rolling(5, min_periods=1).max().astype(bool)\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a4231aa-0152-4aba-b1ff-b40c98f2f196",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond2 = data.get(\"Close\").vbt.crossed_below(bb.middleband)\n",
    "cond2 = cond2.vbt.rolling_any(5)\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "df8a2ad1-3ba1-4b81-8d63-ebca8e656a2b",
   "metadata": {},
   "outputs": [],
   "source": [
    "cond2 = data.get(\"Close\").vbt.crossed_below(bb.middleband)\n",
    "cond2 = cond2.vbt.rolling_apply(\n",
    "    \"W\", \"any\", \n",
    "    minp=1, \n",
    "    wrap_kwargs=dict(fillna=0, dtype=bool)\n",
    ")\n",
    "\n",
    "mask = cond1 & cond2\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39b1b59c-d0ad-4757-bfe6-789a12e11026",
   "metadata": {},
   "outputs": [],
   "source": [
    "anchor_points = data.wrapper.get_index_points(\n",
    "    every=\"M\", \n",
    "    start=0, \n",
    "    exact_start=True\n",
    ")\n",
    "anchor_points"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6b79fd8-b86e-486b-8e87-663e2ddff2c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "left_bound = np.full(len(data.wrapper.index), np.nan)\n",
    "left_bound[anchor_points] = anchor_points\n",
    "left_bound = vbt.dt.to_ns(vbt.nb.ffill_1d_nb(left_bound))\n",
    "left_bound = bandwidth.index[left_bound]\n",
    "left_bound"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "86481972-0ab1-4f65-9fa3-4c3a7f38d3a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "right_bound = data.wrapper.index\n",
    "right_bound"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5177843a-f65a-42ba-9e1d-c1744aee9e7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = (bandwidth <= 0.1).vbt.resample_between_bounds(\n",
    "    left_bound, \n",
    "    right_bound,\n",
    "    \"any\",\n",
    "    closed_lbound=True,\n",
    "    closed_rbound=True,\n",
    "    wrap_with_lbound=False,\n",
    "    wrap_kwargs=dict(fillna=0, dtype=bool)\n",
    ")\n",
    "mask.index = right_bound\n",
    "mask.astype(int).vbt.ts_heatmap().show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73d0ca33-a66b-4fe6-8e7d-144df6bd0882",
   "metadata": {},
   "source": [
    "### Periodically"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a27fa7cc-6c65-4ff4-aebf-aacdaf160ca1",
   "metadata": {},
   "outputs": [],
   "source": [
    "min_data = vbt.BinanceData.pull(\n",
    "    [\"BTCUSDT\", \"ETHUSDT\"], \n",
    "    start=\"2021-01-01 UTC\",\n",
    "    end=\"2021-02-01 UTC\",\n",
    "    timeframe=\"1h\"\n",
    ")\n",
    "index = min_data.wrapper.index\n",
    "tuesday_index = index[index.weekday == 1]\n",
    "tuesday_index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "61283286-8bc4-4a67-8385-54b88926b078",
   "metadata": {},
   "outputs": [],
   "source": [
    "tuesday_1800_index = tuesday_index[tuesday_index.hour == 18]\n",
    "tuesday_1800_index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c1f8c57-df34-4cd0-99b3-f9511f987f99",
   "metadata": {},
   "outputs": [],
   "source": [
    "tuesday_1730_index = index[\n",
    "    (index.weekday == 1) & \n",
    "    (index.hour == 17) & \n",
    "    (index.minute == 30)\n",
    "]\n",
    "tuesday_1730_index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52657759-5799-4aae-ac79-cd588037fcea",
   "metadata": {},
   "outputs": [],
   "source": [
    "index.get_indexer([vbt.timestamp(\"2021-01-07\", tz=index.tz)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e13344f-4c60-4e46-a2ff-9dcecc989475",
   "metadata": {},
   "outputs": [],
   "source": [
    "index.get_indexer([vbt.timestamp(\"2021-01-07 17:30:00\", tz=index.tz)]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bbdd67e5-4b86-4e5a-86f3-d2c123f3bf01",
   "metadata": {},
   "outputs": [],
   "source": [
    "index[index.get_indexer(\n",
    "    [vbt.timestamp(\"2021-01-07 17:30:00\", tz=index.tz)],\n",
    "    method=\"ffill\"\n",
    ")]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "241ed072-2873-4f0b-9cba-7fa5d8bd5401",
   "metadata": {},
   "outputs": [],
   "source": [
    "index[index.get_indexer(\n",
    "    [vbt.timestamp(\"2021-01-07 17:30:00\", tz=index.tz)],\n",
    "    method=\"bfill\"\n",
    ")]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e31841d0-3c4a-4967-a0a1-62b5c680cd85",
   "metadata": {},
   "outputs": [],
   "source": [
    "each_tuesday = vbt.date_range(index[0], index[-1], freq=\"tuesday\")\n",
    "each_tuesday_1730 = each_tuesday + pd.Timedelta(hours=17, minutes=30)\n",
    "each_tuesday_1730"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21d5f7eb-f18e-4327-8d20-9cdc72cad6d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "positions = index.get_indexer(each_tuesday_1730, method=\"bfill\")\n",
    "\n",
    "min_symbol_wrapper = min_data.get_symbol_wrapper()\n",
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask.iloc[positions] = True\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfadfca9-23df-4372-aab6-ecfb9e5c4046",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask[mask.any(axis=1)].index.strftime(\"%A %T\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1206dc67-91f7-4207-ae3e-05e579576fe3",
   "metadata": {},
   "outputs": [],
   "source": [
    "tuesday_after_1700 = (index.weekday == 1) & (index.hour >= 17)\n",
    "wednesday_before_1700 = (index.weekday == 2) & (index.hour < 17)\n",
    "main_cond = tuesday_after_1700 | wednesday_before_1700\n",
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask[main_cond] = True\n",
    "mask = mask.vbt.signals.first()\n",
    "mask[mask.any(axis=1)].index.strftime(\"%A %T\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e011aa6b-94a2-4a76-a3e6-f2998e4bdc5e",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask.vbt.set(\n",
    "    True, \n",
    "    every=\"tuesday\", \n",
    "    at_time=\"17:30\", \n",
    "    inplace=True\n",
    ")\n",
    "mask[mask.any(axis=1)].index.strftime(\"%A %T\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3849cd8e-63ab-4603-b84a-ea2f0e0cb282",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask.vbt.set(\n",
    "    True, \n",
    "    every=\"tuesday\", \n",
    "    at_time=\"18:00\", \n",
    "    add_delta=pd.Timedelta(nanoseconds=1),\n",
    "    inplace=True\n",
    ")\n",
    "mask[mask.any(axis=1)].index.strftime(\"%A %T\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0bcd77bd-b5f9-4e03-8a1b-a2f1646d2887",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask.vbt.set_between(\n",
    "    True, \n",
    "    every=\"monday\", \n",
    "    start_time=\"12:00\", \n",
    "    end_time=\"17:00\", \n",
    "    add_end_delta=pd.Timedelta(days=1),\n",
    "    inplace=True\n",
    ")\n",
    "mask[mask.any(axis=1)].index.strftime(\"%A %T\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0335ab31-4ae0-4e2d-be6e-412949468b6f",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask.vbt.set(\n",
    "    True, \n",
    "    on=\"January 7th 2021 UTC\",\n",
    "    indexer_method=None,\n",
    "    inplace=True\n",
    ")\n",
    "mask[mask.any(axis=1)].index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8383ce7c-dda3-472c-af61-5b1f33b67b3f",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask.vbt.set_between(\n",
    "    True, \n",
    "    start=[\"2021-01-01 12:00:00\", \"2021-01-07 12:00:00\"],\n",
    "    end=[\"2021-01-02 12:00:00\", \"2021-01-08 12:00:00\"],\n",
    "    inplace=True\n",
    ")\n",
    "mask[mask.any(axis=1)].index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1be65183-69dc-4576-925f-ba51c2d9b16d",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = min_symbol_wrapper.fill(False)\n",
    "mask.vbt.set_between(\n",
    "    True, \n",
    "    every=\"monday\",\n",
    "    split_every=False,\n",
    "    add_end_delta=\"2h\",\n",
    "    inplace=True\n",
    ")\n",
    "mask[mask.any(axis=1)].index"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0942b710-9089-4203-812c-087c90458e50",
   "metadata": {},
   "source": [
    "### Iteratively"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9da35717-67da-421a-acf7-e3e1a81d34f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def generate_mask_1d_nb(\n",
    "    high, low,\n",
    "    uband, mband, lband,\n",
    "    cond2_th, cond4_th\n",
    "):\n",
    "    out = np.full(high.shape, False)\n",
    "    \n",
    "    for i in range(high.shape[0]):\n",
    "\n",
    "      \n",
    "        bandwidth = (uband[i] - lband[i]) / mband[i]\n",
    "        cond1 = low[i] < lband[i]\n",
    "        cond2 = bandwidth > cond2_th\n",
    "        cond3 = high[i] > uband[i]\n",
    "        cond4 = bandwidth < cond4_th\n",
    "        signal = (cond1 and cond2) or (cond3 and cond4)\n",
    "        \n",
    "        out[i] = signal\n",
    "        \n",
    "    return out\n",
    "\n",
    "mask = generate_mask_1d_nb(\n",
    "    data.get(\"High\")[\"BTCUSDT\"].values,\n",
    "    data.get(\"Low\")[\"BTCUSDT\"].values,\n",
    "    bb.upperband[\"BTCUSDT\"].values,\n",
    "    bb.middleband[\"BTCUSDT\"].values,\n",
    "    bb.lowerband[\"BTCUSDT\"].values,\n",
    "    0.30,\n",
    "    0.15\n",
    ")\n",
    "symbol_wrapper = data.get_symbol_wrapper()\n",
    "mask = symbol_wrapper[\"BTCUSDT\"].wrap(mask)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff27246a-d96c-428a-a391-56decc8a457c",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def generate_mask_nb(\n",
    "    high, low,\n",
    "    uband, mband, lband,\n",
    "    cond2_th, cond4_th\n",
    "):\n",
    "    out = np.empty(high.shape, dtype=np.bool_)\n",
    "    \n",
    "    for col in range(high.shape[1]):\n",
    "        out[:, col] = generate_mask_1d_nb(\n",
    "            high[:, col], low[:, col],\n",
    "            uband[:, col], mband[:, col], lband[:, col],\n",
    "            cond2_th, cond4_th\n",
    "        )\n",
    "        \n",
    "    return out\n",
    "\n",
    "mask = generate_mask_nb(\n",
    "    vbt.to_2d_array(data.get(\"High\")),\n",
    "    vbt.to_2d_array(data.get(\"Low\")),\n",
    "    vbt.to_2d_array(bb.upperband),\n",
    "    vbt.to_2d_array(bb.middleband),\n",
    "    vbt.to_2d_array(bb.lowerband),\n",
    "    0.30,\n",
    "    0.15\n",
    ")\n",
    "mask = symbol_wrapper.wrap(mask)\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e38705d-863f-40c7-bfad-127b7551999d",
   "metadata": {},
   "outputs": [],
   "source": [
    "MaskGenerator = vbt.IF(\n",
    "    input_names=[\"high\", \"low\", \"uband\", \"mband\", \"lband\"],\n",
    "    param_names=[\"cond2_th\", \"cond4_th\"],\n",
    "    output_names=[\"mask\"]\n",
    ").with_apply_func(generate_mask_1d_nb, takes_1d=True)\n",
    "mask_generator = MaskGenerator.run(\n",
    "    data.get(\"High\"),\n",
    "    data.get(\"Low\"),\n",
    "    bb.upperband,\n",
    "    bb.middleband,\n",
    "    bb.lowerband,\n",
    "    [0.3, 0.4],\n",
    "    [0.1, 0.2],\n",
    "    param_product=True\n",
    ")\n",
    "mask_generator.mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2989ca1-0925-4d8f-bfb1-82606aed0d80",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def value_ago_1d_nb(arr, ago):\n",
    "    out = np.empty(arr.shape, dtype=np.float_)\n",
    "    for i in range(out.shape[0]):\n",
    "        if i - ago >= 0:\n",
    "            out[i] = arr[i - ago]\n",
    "        else:\n",
    "            out[i] = np.nan\n",
    "    return out\n",
    "\n",
    "arr = np.array([1, 2, 3])\n",
    "value_ago_1d_nb(arr, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24455bb7-98c0-45c1-9333-78c9424838ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def any_in_window_1d_nb(arr, window):\n",
    "    out = np.empty(arr.shape, dtype=np.bool_)\n",
    "    for i in range(out.shape[0]):\n",
    "        from_i = max(0, i + 1 - window)\n",
    "        to_i = i + 1\n",
    "        out[i] = np.any(arr[from_i:to_i])\n",
    "    return out\n",
    "\n",
    "arr = np.array([False, True, True, False, False])\n",
    "any_in_window_1d_nb(arr, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a4cf9d70-b97c-4c47-b65a-8a4c13f4ab9f",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def any_in_var_window_1d_nb(arr, index, freq):\n",
    "    out = np.empty(arr.shape, dtype=np.bool_)\n",
    "    from_i = 0\n",
    "    for i in range(out.shape[0]):\n",
    "        if index[from_i] <= index[i] - freq:\n",
    "            for j in range(from_i + 1, index.shape[0]):\n",
    "                if index[j] > index[i] - freq:\n",
    "                    from_i = j\n",
    "                    break\n",
    "        to_i = i + 1\n",
    "        out[i] = np.any(arr[from_i:to_i])\n",
    "    return out\n",
    "\n",
    "arr = np.array([False, True, True, False, False])\n",
    "index = vbt.date_range(\"2020\", freq=\"5min\", periods=len(arr)).values\n",
    "freq = vbt.timedelta(\"10min\").to_timedelta64()\n",
    "any_in_var_window_1d_nb(arr, index, freq)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3fd6c643-a018-4f56-a52d-2352a5ecb430",
   "metadata": {},
   "outputs": [],
   "source": [
    "any_in_var_window_1d_nb(arr, vbt.dt.to_ns(index), vbt.dt.to_ns(freq))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a375903-eea0-4bd1-b46f-d71c0dbc719c",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.dt.to_ns(index)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e32610d0-01e8-4d4d-a3e6-b83218a8e877",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.dt.to_ns(index - np.datetime64(0, \"ns\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b775b6a0-2e64-4027-b84a-9aed88e0ae66",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.dt.to_ns(freq)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20a5f083-2f52-436e-9d23-3a0ef67d929d",
   "metadata": {},
   "outputs": [],
   "source": [
    "vbt.dt.to_ns(freq) / 1000 / 1000 / 1000 / 60"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1dc72ab-3858-49a0-93c2-a7d37c8073c7",
   "metadata": {},
   "source": [
    "### Generators"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b6bb839a-61f5-44be-8643-495078dbd208",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def place_func_nb(c, index):\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        weekday = vbt.dt_nb.weekday_nb(index[i])\n",
    "        hour = vbt.dt_nb.hour_nb(index[i])\n",
    "        if weekday == 2 and hour == 17:\n",
    "            c.out[out_i] = True\n",
    "            return out_i\n",
    "    return -1\n",
    "\n",
    "mask = vbt.pd_acc.signals.generate(\n",
    "    symbol_wrapper.shape,\n",
    "    place_func_nb,\n",
    "    vbt.dt.to_ns(symbol_wrapper.index),\n",
    "    wrapper=symbol_wrapper\n",
    ")\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3124c4b-c02c-4e0e-9362-5c5b8114dd1b",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def place_func_nb(c, index):\n",
    "    last_i = -1\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        weekday = vbt.dt_nb.weekday_nb(index[i])\n",
    "        hour = vbt.dt_nb.hour_nb(index[i])\n",
    "        if weekday == 2 and hour == 17:\n",
    "            c.out[out_i] = True\n",
    "            last_i = out_i\n",
    "        else:\n",
    "            past_target_midnight = vbt.dt_nb.past_weekday_nb(index[i], 2)\n",
    "            past_target = past_target_midnight + 17 * vbt.dt_nb.h_ns\n",
    "            if (i > 0 and index[i - 1] < past_target) and \\\n",
    "                index[i] > past_target:\n",
    "                c.out[out_i] = True\n",
    "                last_i = out_i\n",
    "    return last_i\n",
    "\n",
    "mask = vbt.pd_acc.signals.generate(\n",
    "    symbol_wrapper.shape,\n",
    "    place_func_nb,\n",
    "    vbt.dt.to_ns(symbol_wrapper.index),\n",
    "    wrapper=symbol_wrapper\n",
    ")\n",
    "mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dce0bdc1-7956-4760-9b29-05cb8a7d13b6",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask.index[mask.any(axis=1)].strftime('%A %m/%d/%Y')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1a5a0e2-23cd-48d3-ab36-2f94d8a03adc",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def place_func_nb(c, weekday, hour, index):\n",
    "    last_i = -1\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        weekday_now = vbt.dt_nb.weekday_nb(index[i])\n",
    "        hour_now = vbt.dt_nb.hour_nb(index[i])\n",
    "        if weekday_now == weekday and hour_now == hour:\n",
    "            c.out[out_i] = True\n",
    "            last_i = out_i\n",
    "    return last_i\n",
    "\n",
    "EntryGenerator = vbt.SignalFactory(\n",
    "    mode=\"entries\",\n",
    "    param_names=[\"weekday\", \"hour\"]\n",
    ").with_place_func(\n",
    "    entry_place_func_nb=place_func_nb,\n",
    "    entry_settings=dict(\n",
    "        pass_params=[\"weekday\", \"hour\"],\n",
    "    ),\n",
    "    var_args=True\n",
    ")\n",
    "entry_generator = EntryGenerator.run(\n",
    "    symbol_wrapper.shape,\n",
    "    2, \n",
    "    [0, 17],\n",
    "    vbt.dt.to_ns(symbol_wrapper.index),\n",
    "    input_index=symbol_wrapper.index,\n",
    "    input_columns=symbol_wrapper.columns\n",
    ")\n",
    "entry_generator.entries.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f29a5bde-9025-4946-932d-94d23e4b5ac0",
   "metadata": {},
   "outputs": [],
   "source": [
    "entry_generator.plot(column=(2, 0, \"BTCUSDT\")).show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21f7b441-5d5a-4ac9-93bb-e0289eefa5d6",
   "metadata": {},
   "source": [
    "#### Exits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1a58d3a9-e3fa-4839-908d-e6d8a0009e04",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def exit_place_func_nb(c):\n",
    "    c.out[0] = True\n",
    "    return 0\n",
    "\n",
    "entries = symbol_wrapper.fill(False)\n",
    "entries.vbt.set(True, every=\"Q\", inplace=True)\n",
    "entries.index[entries.any(axis=1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03872da1-6712-4f1e-809d-92303661364e",
   "metadata": {},
   "outputs": [],
   "source": [
    "exits = entries.vbt.signals.generate_exits(exit_place_func_nb)\n",
    "exits.index[exits.any(axis=1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c80edcb4-602b-48e8-bf7b-98e805259758",
   "metadata": {},
   "outputs": [],
   "source": [
    "exits = entries.vbt.signals.generate_exits(\n",
    "    exit_place_func_nb,\n",
    "    wait=0\n",
    ")\n",
    "exits.index[exits.any(axis=1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02c6a9cc-13f0-4998-8992-61110ea2d66f",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def exit_place_func_nb(c, index, wait_td):\n",
    "    last_i = -1\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        if index[i] >= index[c.from_i] + wait_td:\n",
    "            c.out[out_i] = True\n",
    "            last_i = out_i\n",
    "            break\n",
    "    return last_i\n",
    "\n",
    "exits = entries.vbt.signals.generate_exits(\n",
    "    exit_place_func_nb,\n",
    "    vbt.dt.to_ns(entries.index),\n",
    "    vbt.dt.to_ns(vbt.timedelta(\"7d\")),\n",
    "    wait=0\n",
    ")\n",
    "exits.index[exits.any(axis=1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da4bdca3-d5e6-47f4-bf53-9ac89228df0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries = symbol_wrapper.fill(False)\n",
    "entries.vbt.set(True, every=\"5d\", inplace=True)\n",
    "exits = entries.vbt.signals.generate_exits(\n",
    "    exit_place_func_nb,\n",
    "    vbt.dt.to_ns(entries.index),\n",
    "    vbt.dt.to_ns(vbt.timedelta(\"7d\")),\n",
    "    wait=0\n",
    ")\n",
    "exits.index[exits.any(axis=1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6690a670-24a5-4eb9-8552-16f845ab3c50",
   "metadata": {},
   "outputs": [],
   "source": [
    "exits = entries.vbt.signals.generate_exits(\n",
    "    exit_place_func_nb,\n",
    "    vbt.dt.to_ns(entries.index),\n",
    "    vbt.dt.to_ns(vbt.timedelta(\"7d\")),\n",
    "    wait=0,\n",
    "    until_next=False\n",
    ")\n",
    "exits.index[exits.any(axis=1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3729138-f008-4371-883f-31240e1aadb3",
   "metadata": {},
   "outputs": [],
   "source": [
    "exits = entries.vbt.signals.generate_exits(\n",
    "    exit_place_func_nb,\n",
    "    vbt.dt.to_ns(entries.index),\n",
    "    vbt.dt.to_ns(vbt.timedelta(\"7d\")),\n",
    "    wait=0,\n",
    "    until_next=False,\n",
    "    skip_until_exit=True\n",
    ")\n",
    "exits.index[exits.any(axis=1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "939bb536-1973-4798-9f9e-bb65314a6f31",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def exit_place_func_nb(c, wait_td, index):\n",
    "    last_i = -1\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        if index[i] >= index[c.from_i] + wait_td:\n",
    "            c.out[out_i] = True\n",
    "            last_i = out_i\n",
    "            break\n",
    "    return last_i\n",
    "\n",
    "ExitGenerator = vbt.SignalFactory(\n",
    "    mode=\"exits\",\n",
    "    param_names=[\"wait_td\"]\n",
    ").with_place_func(\n",
    "    exit_place_func_nb=exit_place_func_nb,\n",
    "    exit_settings=dict(\n",
    "        pass_params=[\"wait_td\"],\n",
    "    ),\n",
    "    var_args=True,\n",
    "    wait=0,\n",
    "    until_next=False,\n",
    "    skip_until_exit=True,\n",
    "    param_settings=dict(\n",
    "        wait_td=dict(\n",
    "            post_index_func=lambda x: x.map(lambda y: str(vbt.timedelta(y)))\n",
    "        )\n",
    "    ),\n",
    ")\n",
    "exit_generator = ExitGenerator.run(\n",
    "    entries,\n",
    "    [\n",
    "        vbt.timedelta(\"3d\").to_timedelta64(),\n",
    "        vbt.timedelta(\"7d\").to_timedelta64()\n",
    "    ],\n",
    "    symbol_wrapper.index.values\n",
    ")\n",
    "exit_generator.exits.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de964714-e321-4d79-bfe1-b466a087b293",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_entries = exit_generator.entries.vbt.signals.first(\n",
    "    reset_by=exit_generator.exits,\n",
    "    allow_gaps=True, \n",
    ")\n",
    "new_entries.index[new_entries[(\"7 days 00:00:00\", \"BTCUSDT\")]]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b97e203-9f56-4390-a5cd-30ee65beb85f",
   "metadata": {},
   "source": [
    "#### Both"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d838e1e5-0060-4a04-8c33-4c1f96e701a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def entry_place_func_nb(c, low, close, th):\n",
    "    if c.from_i == 0:\n",
    "        c.out[0] = True\n",
    "        return 0\n",
    "    exit_price = close[c.from_i - c.wait, c.col]\n",
    "    hit_price = exit_price * (1 - th)\n",
    "    last_i = -1\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        if low[i, c.col] <= hit_price:\n",
    "            c.out[out_i] = True\n",
    "            last_i = out_i\n",
    "            break\n",
    "    return last_i\n",
    "\n",
    "@njit\n",
    "def exit_place_func_nb(c, high, close, th):\n",
    "    entry_price = close[c.from_i - c.wait, c.col]\n",
    "    hit_price = entry_price * (1 + th)\n",
    "    last_i = -1\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        if high[i, c.col] >= hit_price:\n",
    "            c.out[out_i] = True\n",
    "            last_i = out_i\n",
    "            break\n",
    "    return last_i\n",
    "\n",
    "entries, exits = vbt.pd_acc.signals.generate_both(\n",
    "    symbol_wrapper.shape,\n",
    "    entry_place_func_nb=entry_place_func_nb,\n",
    "    entry_place_args=(vbt.Rep(\"low\"), vbt.Rep(\"close\"), 0.1),\n",
    "    exit_place_func_nb=exit_place_func_nb,\n",
    "    exit_place_args=(vbt.Rep(\"high\"), vbt.Rep(\"close\"), 0.2),\n",
    "    wrapper=symbol_wrapper,\n",
    "    broadcast_named_args=dict(\n",
    "        high=data.get(\"High\"),\n",
    "        low=data.get(\"Low\"),\n",
    "        close=data.get(\"Close\")\n",
    "    ),\n",
    "    broadcast_kwargs=dict(\n",
    "        post_func=vbt.to_2d_array\n",
    "    )\n",
    ")\n",
    "\n",
    "fig = data.plot(\n",
    "    symbol=\"BTCUSDT\", \n",
    "    ohlc_trace_kwargs=dict(opacity=0.5), \n",
    "    plot_volume=False\n",
    ")\n",
    "entries[\"BTCUSDT\"].vbt.signals.plot_as_entries(\n",
    "    y=data.get(\"Close\", \"BTCUSDT\"), fig=fig)\n",
    "exits[\"BTCUSDT\"].vbt.signals.plot_as_exits(\n",
    "    y=data.get(\"Close\", \"BTCUSDT\"), fig=fig)\n",
    "fig.show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1241ad12-8874-43f9-868f-337dba360343",
   "metadata": {},
   "outputs": [],
   "source": [
    "BothGenerator = vbt.SignalFactory(\n",
    "    mode=\"both\",\n",
    "    input_names=[\"high\", \"low\", \"close\"],\n",
    "    param_names=[\"entry_th\", \"exit_th\"]\n",
    ").with_place_func(\n",
    "    entry_place_func_nb=entry_place_func_nb,\n",
    "    entry_settings=dict(\n",
    "        pass_inputs=[\"low\", \"close\"],\n",
    "        pass_params=[\"entry_th\"],\n",
    "    ),\n",
    "    exit_place_func_nb=exit_place_func_nb,\n",
    "    exit_settings=dict(\n",
    "        pass_inputs=[\"high\", \"close\"],\n",
    "        pass_params=[\"exit_th\"],\n",
    "    )\n",
    ")\n",
    "both_generator = BothGenerator.run(\n",
    "    data.get(\"High\"),\n",
    "    data.get(\"Low\"),\n",
    "    data.get(\"Close\"),\n",
    "    [0.1, 0.2],\n",
    "    [0.2, 0.3],\n",
    "    param_product=True\n",
    ")\n",
    "fig = data.plot(\n",
    "    symbol=\"BTCUSDT\", \n",
    "    ohlc_trace_kwargs=dict(opacity=0.5), \n",
    "    plot_volume=False\n",
    ")\n",
    "both_generator.plot(\n",
    "    column=(0.1, 0.3, \"BTCUSDT\"), \n",
    "    entry_y=data.get(\"Close\", \"BTCUSDT\"), \n",
    "    exit_y=data.get(\"Close\", \"BTCUSDT\"), \n",
    "    fig=fig\n",
    ").show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3e76f00-2927-4af1-90e3-2c288f81225a",
   "metadata": {},
   "source": [
    "#### Chained exits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92d515cf-394e-4b2a-b5c9-8a7c702e9f45",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def exit_place_func_nb(c, low, request_price, fill_price_out):\n",
    "    _request_price = request_price[c.from_i - c.wait, c.col]\n",
    "    last_i = -1\n",
    "    for out_i in range(len(c.out)):\n",
    "        i = c.from_i + out_i\n",
    "        if low[i, c.col] <= _request_price:\n",
    "            fill_price_out[i, c.col] = _request_price\n",
    "            c.out[out_i] = True\n",
    "            last_i = out_i\n",
    "            break\n",
    "    return last_i\n",
    "\n",
    "ChainGenerator = vbt.SignalFactory(\n",
    "    mode=\"chain\",\n",
    "    input_names=[\"low\", \"request_price\"],\n",
    "    in_output_names=[\"fill_price_out\"]\n",
    ").with_place_func(\n",
    "    exit_place_func_nb=exit_place_func_nb,\n",
    "    exit_settings=dict(\n",
    "        pass_inputs=[\"low\", \"request_price\"],\n",
    "        pass_in_outputs=[\"fill_price_out\"],\n",
    "    ),\n",
    "    fill_price_out=np.nan\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66e50dd1-4984-432a-8a3e-4ca8fa6a6a29",
   "metadata": {},
   "outputs": [],
   "source": [
    "fast_ma = vbt.talib(\"SMA\").run(\n",
    "    data.get(\"Close\"), \n",
    "    vbt.Default(10), \n",
    "    short_name=\"fast_ma\"\n",
    ")\n",
    "slow_ma = vbt.talib(\"SMA\").run(\n",
    "    data.get(\"Close\"), \n",
    "    vbt.Default(20), \n",
    "    short_name=\"slow_ma\"\n",
    ")\n",
    "entries = fast_ma.real_crossed_above(slow_ma)\n",
    "entries.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45ad9fbd-9250-463a-b599-7c69eeb1247d",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain_generator = ChainGenerator.run(\n",
    "    entries,\n",
    "    data.get(\"Low\"),\n",
    "    data.get(\"Close\") * (1 - 0.1)\n",
    ")\n",
    "request_mask = chain_generator.new_entries\n",
    "request_mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f7a80bb3-b778-45df-a375-0df6672cf36b",
   "metadata": {},
   "outputs": [],
   "source": [
    "request_price = chain_generator.request_price\n",
    "print(request_price[request_mask.any(axis=1)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd9c6dd2-5da2-437f-b750-050b031160a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "fill_mask = chain_generator.exits\n",
    "fill_mask.sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0691fbf4-4b08-43c0-a8ae-6075bdf0af12",
   "metadata": {},
   "outputs": [],
   "source": [
    "fill_price = chain_generator.fill_price_out\n",
    "print(fill_price[fill_mask.any(axis=1)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0566319a-0cb4-4dd1-81f8-3ed7d2d5f9d6",
   "metadata": {},
   "source": [
    "### Preset generators"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07b9bd34-8bc7-4383-9b3b-13d8fbb7ae99",
   "metadata": {},
   "source": [
    "#### Random"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0081e390-e918-4edb-ac65-297da562a4a2",
   "metadata": {},
   "outputs": [],
   "source": [
    "btcusdt_wrapper = symbol_wrapper[\"BTCUSDT\"]\n",
    "mask = vbt.pd_acc.signals.generate_random(\n",
    "    btcusdt_wrapper.shape,\n",
    "    prob=1 / 10,\n",
    "    wrapper=btcusdt_wrapper,\n",
    "    seed=42\n",
    ")\n",
    "mask_index = mask.index[mask]\n",
    "(mask_index[1:] - mask_index[:-1]).mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9cca388d-e1d0-4c83-a86b-ec7087d34e05",
   "metadata": {},
   "outputs": [],
   "source": [
    "monday_mask = btcusdt_wrapper.fill(False)\n",
    "monday_mask.vbt.set(True, every=\"monday\", inplace=True)\n",
    "mask = monday_mask.vbt.signals.generate_random_exits(wait=0)\n",
    "mask_index = mask.index[mask]\n",
    "mask_index.strftime(\"%W %A\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5df8383a-7c16-4d54-8b7a-6695d9b24546",
   "metadata": {},
   "outputs": [],
   "source": [
    "prob = np.linspace(0, 1, len(symbol_wrapper.index))\n",
    "rprob = vbt.RPROB.run(\n",
    "    symbol_wrapper.shape,\n",
    "    vbt.Default(vbt.to_2d_pr_array(prob)),\n",
    "    seed=42,\n",
    "    input_index=symbol_wrapper.index,\n",
    "    input_columns=symbol_wrapper.columns\n",
    ")\n",
    "rprob.entries.astype(int).vbt.ts_heatmap().show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ecf3d7f-ecb2-4cda-bd89-756d0a278c4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "rprob = vbt.RPROB.run(\n",
    "    symbol_wrapper.shape,\n",
    "    [0.5, vbt.to_2d_pr_array(prob)],\n",
    "    seed=42,\n",
    "    input_index=symbol_wrapper.index,\n",
    "    input_columns=symbol_wrapper.columns\n",
    ")\n",
    "rprob.entries.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1646593e-9669-4757-8df9-3713a8e46ad6",
   "metadata": {},
   "source": [
    "#### Stops"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ecf076e4-697a-4da7-8afa-287820c2c1da",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_entries, exits = entries.vbt.signals.generate_stop_exits(\n",
    "    data.get(\"Close\"),\n",
    "    data.get(\"High\"),\n",
    "    stop=0.1,\n",
    "    chain=True\n",
    ")\n",
    "print(new_entries[new_entries.any(axis=1)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe64925a-0fc3-42fa-b128-7f92cb650456",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(exits[exits.any(axis=1)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e84b01df-1e52-4b51-8d91-5a057bed798a",
   "metadata": {},
   "outputs": [],
   "source": [
    "out_dict = {}\n",
    "new_entries, exits = entries.vbt.signals.generate_stop_exits(\n",
    "    data.get(\"Close\"),\n",
    "    data.get(\"High\"),\n",
    "    stop=0.1,\n",
    "    chain=True,\n",
    "    out_dict=out_dict\n",
    ")\n",
    "print(out_dict[\"stop_ts\"][exits.any(axis=1)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92b3b055-6b0d-4968-8042-943ad3612b38",
   "metadata": {},
   "outputs": [],
   "source": [
    "stcx = vbt.STCX.run(\n",
    "    entries,\n",
    "    data.get(\"Open\"),\n",
    "    ts=data.get(\"Low\"),\n",
    "    follow_ts=data.get(\"High\"),\n",
    "    stop=-0.1,\n",
    "    trailing=[False, True],\n",
    "    wait=0\n",
    ")\n",
    "fig = data.plot(\n",
    "    symbol=\"BTCUSDT\", \n",
    "    ohlc_trace_kwargs=dict(opacity=0.5), \n",
    "    plot_volume=False\n",
    ")\n",
    "stcx.plot(\n",
    "    column=(-0.1, True, \"BTCUSDT\"), \n",
    "    entry_y=\"entry_ts\",\n",
    "    exit_y=\"stop_ts\", \n",
    "    fig=fig\n",
    ").show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ef68d113-7f90-4b68-810b-d31f6f13e1ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "ohlcstcx = vbt.OHLCSTCX.run(\n",
    "    entries,\n",
    "    data.get(\"Close\"),\n",
    "    data.get(\"Open\"),\n",
    "    data.get(\"High\"),\n",
    "    data.get(\"Low\"),\n",
    "    data.get(\"Close\"),\n",
    "    sl_stop=vbt.Default(0.1),\n",
    "    tsl_stop=vbt.Default(0.15),\n",
    "    is_entry_open=False\n",
    ")\n",
    "ohlcstcx.plot(column=(\"BTCUSDT\")).show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "222b01c5-3d86-4c5a-9c1e-dbfdd568407f",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ohlcstcx.stop_type_readable[ohlcstcx.exits.any(axis=1)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b7d0fdc-e793-46be-8559-398c418ccffc",
   "metadata": {},
   "outputs": [],
   "source": [
    "ohlcstcx = vbt.OHLCSTCX.run(\n",
    "    entries,\n",
    "    data.get(\"Close\"),\n",
    "    sl_stop=vbt.Default(0.1),\n",
    "    tsl_stop=vbt.Default(0.15),\n",
    "    is_entry_open=False\n",
    ")\n",
    "ohlcstcx.plot(column=(\"BTCUSDT\")).show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "04992035-5ab7-47c5-b4c1-468bc22221ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "entry_pos_rank = entries.vbt.signals.pos_rank(allow_gaps=True)\n",
    "short_entries = (entry_pos_rank >= 0) & (entry_pos_rank % 2 == 1)\n",
    "\n",
    "ohlcstcx = vbt.OHLCSTCX.run(\n",
    "    entries,\n",
    "    data.get(\"Close\"),\n",
    "    data.get(\"Open\"),\n",
    "    data.get(\"High\"),\n",
    "    data.get(\"Low\"),\n",
    "    data.get(\"Close\"),\n",
    "    tsl_th=vbt.Default(0.2),\n",
    "    tsl_stop=vbt.Default(0.1),\n",
    "    reverse=vbt.Default(short_entries),\n",
    "    is_entry_open=False\n",
    ")\n",
    "ohlcstcx.plot(column=(\"BTCUSDT\")).show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d5b66ff-5a7f-46da-b515-a8d84c1c2334",
   "metadata": {},
   "outputs": [],
   "source": [
    "long_entries = ohlcstcx.new_entries.vbt & (~short_entries)\n",
    "long_exits = ohlcstcx.exits.vbt.signals.first_after(long_entries)\n",
    "short_entries = ohlcstcx.new_entries.vbt & short_entries\n",
    "short_exits = ohlcstcx.exits.vbt.signals.first_after(short_entries)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad0ad046-3df7-4872-b943-03049982ce10",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = data.plot(\n",
    "    symbol=\"BTCUSDT\", \n",
    "    ohlc_trace_kwargs=dict(opacity=0.5), \n",
    "    plot_volume=False\n",
    ")\n",
    "long_entries[\"BTCUSDT\"].vbt.signals.plot_as_entries(\n",
    "    ohlcstcx.entry_price[\"BTCUSDT\"],\n",
    "    trace_kwargs=dict(marker=dict(color=\"limegreen\"), name=\"Long entries\"), \n",
    "    fig=fig\n",
    ")\n",
    "long_exits[\"BTCUSDT\"].vbt.signals.plot_as_exits(\n",
    "    ohlcstcx.stop_price[\"BTCUSDT\"],\n",
    "    trace_kwargs=dict(marker=dict(color=\"orange\"), name=\"Long exits\"),\n",
    "    fig=fig\n",
    ")\n",
    "short_entries[\"BTCUSDT\"].vbt.signals.plot_as_entries(\n",
    "    ohlcstcx.entry_price[\"BTCUSDT\"],\n",
    "    trace_kwargs=dict(marker=dict(color=\"magenta\"), name=\"Short entries\"),\n",
    "    fig=fig\n",
    ")\n",
    "short_exits[\"BTCUSDT\"].vbt.signals.plot_as_exits(\n",
    "    ohlcstcx.stop_price[\"BTCUSDT\"],\n",
    "    trace_kwargs=dict(marker=dict(color=\"red\"), name=\"Short exits\"),\n",
    "    fig=fig\n",
    ").show_svg()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf356e79-4c3b-4819-ad6e-7f5c556be5fd",
   "metadata": {},
   "source": [
    "## Pre-analysis"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30097f36-14d4-4a9b-bad9-d0cf8b6668f1",
   "metadata": {},
   "source": [
    "### Ranking"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13ca8377-310f-4240-babf-5c99023e3d9f",
   "metadata": {},
   "outputs": [],
   "source": [
    "@njit\n",
    "def rank_func_nb(c):\n",
    "    if c.sig_in_part_cnt == 1:\n",
    "        return 1\n",
    "    return 0\n",
    "\n",
    "sample_mask = pd.Series([True, True, False, True, True])\n",
    "ranked = sample_mask.vbt.signals.rank(rank_func_nb)\n",
    "ranked"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c44fcd0-d678-4ad8-8ef1-99d0f91aad40",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked == 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "693bd2d7-24bf-4c0c-80e4-8665b1649d2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked = sample_mask.vbt.signals.rank(rank_func_nb, after_false=True)\n",
    "ranked == 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ba16d6f-f5fa-4bca-b8a5-fa4891c10587",
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_entries = pd.Series([True, True, True, True, True])\n",
    "sample_exits = pd.Series([False, False, True, False, False])\n",
    "ranked = sample_entries.vbt.signals.rank(\n",
    "    rank_func_nb, \n",
    "    reset_by=sample_exits\n",
    ")\n",
    "ranked == 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "caff048c-a2ae-4e1f-ab1c-15c60a87a7ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked = sample_entries.vbt.signals.rank(\n",
    "    rank_func_nb, \n",
    "    reset_by=sample_exits,\n",
    "    after_reset=True\n",
    ")\n",
    "ranked == 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a973bf32-e451-4786-867e-5c90c82bc6f5",
   "metadata": {},
   "source": [
    "#### Preset rankers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0e2b978-480f-4149-8b31-63004f9ef9b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_mask = pd.Series([True, True, False, True, True])\n",
    "ranked = sample_mask.vbt.signals.pos_rank()\n",
    "ranked"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6415bbd4-2e5d-4484-ab21-0478566513c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked == 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d4b19ce3-2a0e-47ff-bc1b-c1a3695cbcaa",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked = sample_mask.vbt.signals.pos_rank(allow_gaps=True)\n",
    "ranked"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "369d7a73-c8cb-4579-8f42-28510b948f63",
   "metadata": {},
   "outputs": [],
   "source": [
    "(ranked > -1) & (ranked % 2 == 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50d51cf9-c42b-4468-a54f-be011327e474",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked = sample_mask.vbt.signals.partition_pos_rank(allow_gaps=True)\n",
    "ranked"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d4864bb-e549-4016-bced-cd39ca4b5b70",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked == 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c624d629-f1fc-4c33-ad43-5c85496cb483",
   "metadata": {},
   "outputs": [],
   "source": [
    "entry_cond1 = data.get(\"Low\") < bb.lowerband\n",
    "entry_cond2 = bandwidth > 0.3\n",
    "entry_cond3 = data.get(\"High\") > bb.upperband\n",
    "entry_cond4 = bandwidth < 0.15\n",
    "entries = (entry_cond1 & entry_cond2) | (entry_cond3 & entry_cond4)\n",
    "\n",
    "entries.vbt.signals.from_nth(0).sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a8b250d1-ba86-44ba-b532-cd42d261545a",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries.vbt.signals.from_nth(1).sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "047fbe11-5da9-439f-b5c5-b1109f32fcbf",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries.vbt.signals.from_nth(2).sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80f7a1d3-16eb-4aee-bb06-91cc09556a8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "exit_cond1 = data.get(\"High\") > bb.upperband\n",
    "exit_cond2 = bandwidth > 0.3\n",
    "exit_cond3 = data.get(\"Low\") < bb.lowerband\n",
    "exit_cond4 = bandwidth < 0.15\n",
    "exits = (exit_cond1 & exit_cond2) | (exit_cond3 & exit_cond4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24028cc0-e4f5-4025-a107-38d92c01c4c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "exits.vbt.signals.pos_rank_after(entries, reset_wait=0).max() + 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3a4b2be2-8e5c-463a-9655-1f420b0017c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries.vbt.signals.pos_rank_after(exits).max() + 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0beeb7b-b5c4-4f48-9791-8a0828e956ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranked = exits.vbt.signals.pos_rank_after(entries, reset_wait=0)\n",
    "highest_ranked = ranked == ranked.max()\n",
    "print(ranked[highest_ranked.any(axis=1)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "805ef372-1748-4dc4-825a-46e3f55fb3f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "exits_after = exits.vbt.signals.from_nth_after(0, entries, reset_wait=0)\n",
    "(exits ^ exits_after).sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0db70553-be70-4a1c-9ce4-92f906ba291b",
   "metadata": {},
   "source": [
    "#### Mapped ranks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9aeaa467-6a09-4a45-8c4a-abd587c7148d",
   "metadata": {},
   "outputs": [],
   "source": [
    "mask = bandwidth.vbt > vbt.Param(np.arange(1, 10) / 10, name=\"bw_th\")\n",
    "mapped_ranks = mask.vbt.signals.pos_rank(as_mapped=True)\n",
    "mapped_ranks.max(group_by=vbt.ExceptLevel(\"symbol\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ef8deef-a5d9-40b0-9819-78dbac8f5b27",
   "metadata": {},
   "source": [
    "### Cleaning"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "402c23ed-0a63-4b9c-b215-ac7dacf083e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_exits = exits.vbt.signals.first_after(entries, reset_wait=0)\n",
    "new_entries = entries.vbt.signals.first_after(exits)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b79491ce-3291-46ee-a2a6-5aa3ac9a2de3",
   "metadata": {},
   "outputs": [],
   "source": [
    "symbol = \"ETHUSDT\"\n",
    "fig = data.plot(\n",
    "    symbol=symbol, \n",
    "    ohlc_trace_kwargs=dict(opacity=0.5), \n",
    "    plot_volume=False\n",
    ")\n",
    "entries[symbol].vbt.signals.plot_as_entries(\n",
    "    y=data.get(\"Close\", symbol), fig=fig)\n",
    "exits[symbol].vbt.signals.plot_as_exits(\n",
    "    y=data.get(\"Close\", symbol), fig=fig)\n",
    "new_entries[symbol].vbt.signals.plot_as_entry_marks(\n",
    "    y=data.get(\"Close\", symbol), fig=fig, \n",
    "    trace_kwargs=dict(name=\"New entries\"))\n",
    "new_exits[symbol].vbt.signals.plot_as_exit_marks(\n",
    "    y=data.get(\"Close\", symbol), fig=fig, \n",
    "    trace_kwargs=dict(name=\"New exits\")).show_svg()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "883ff6e0-7777-487e-b58b-80bc38f2634c",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_entries, new_exits = entries.vbt.signals.clean(exits)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82b4e080-808a-4ab3-a700-81ecf0b75d98",
   "metadata": {},
   "source": [
    "### Duration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1b9ac018-2643-4ef5-aded-a617062e726a",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranges = entries.vbt.signals.between_ranges()\n",
    "print(ranges.records)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aa971658-1359-403b-9e65-a4a3ce390751",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranges.start_idx.min(wrap_kwargs=dict(to_index=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "35efd36c-c2f2-43c0-af21-9df040ec7cc8",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ranges.duration.describe(wrap_kwargs=dict(to_timedelta=True)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b944e41-0a8f-498e-82ce-30c46d4b2db6",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranges = entries.vbt.signals.between_ranges(target=exits)\n",
    "ranges.avg_duration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b920e56-d16d-400a-9382-a23d63162fda",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_ranges = new_entries.vbt.signals.between_ranges(target=new_exits)\n",
    "new_ranges.avg_duration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8528ab4c-22b6-4af0-87f5-5c6063bf68c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranges = entries.vbt.signals.between_ranges(target=exits, relation=\"manyone\")\n",
    "ranges.avg_duration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a047b2b8-a27c-4ac0-b124-9f6cfbe513b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_ranges = new_entries.vbt.signals.between_ranges(target=new_exits, relation=\"manyone\")\n",
    "new_ranges.avg_duration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ab9a53b-2f2e-4601-8e58-c04d86892ee5",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranges = entries.vbt.signals.partition_ranges()\n",
    "print(ranges.duration.describe())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c35fb94f-04e8-4e6f-a2a4-1f1542d02edb",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_ranges = new_entries.vbt.signals.partition_ranges()\n",
    "print(new_ranges.duration.describe())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5cdef2ce-f8ef-44c6-8eac-42172560d93c",
   "metadata": {},
   "outputs": [],
   "source": [
    "ranges = entries.vbt.signals.between_partition_ranges()\n",
    "print(ranges.duration.describe(wrap_kwargs=dict(to_timedelta=True)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "541e5a28-aa4f-4056-b4de-6ea7ecdb9064",
   "metadata": {},
   "source": [
    "### Overview"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe7bcbc0-4300-4c7b-a4d6-17e903bb18bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries.vbt.signals.stats(column=\"BTCUSDT\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a650256c-a112-4ef2-9480-b867ae5b1892",
   "metadata": {},
   "outputs": [],
   "source": [
    "entries.vbt.signals.stats(column=\"BTCUSDT\", settings=dict(target=exits))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1cf7ad48-e516-4f88-897b-29cc5a0f7d64",
   "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.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
