{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "e18a6831",
      "metadata": {},
      "source": [
        "# Inline Uploaded Exposures\n",
        "\n",
        "Use an inline uploaded exposure group when you have your own factor exposures and want to see them as factors in a model on an existing dataset straight away, without building or updating a risk dataset. The group is resolved while the report runs, so the loop is \"re-upload, rerun\" instead of \"re-upload, rebuild the dataset, rerun\"."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "7b69a928",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import polars as pl\n",
        "\n",
        "from bayesline.api.equity import (\n",
        "    CategoricalExposureGroupSettings,\n",
        "    CategoricalFilterSettings,\n",
        "    ContinuousExposureGroupSettings,\n",
        "    ExposureSettings,\n",
        "    FactorRiskModelSettings,\n",
        "    InlineUploadedExposureGroupSettings,\n",
        "    ModelConstructionSettings,\n",
        "    UniverseSettings,\n",
        ")\n",
        "from bayesline.apiclient import BayeslineApiClient"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "61148405",
      "metadata": {
        "tags": [
          "skip-execution"
        ]
      },
      "outputs": [],
      "source": [
        "bln = BayeslineApiClient.new_client(\n",
        "    endpoint=\"https://[ENDPOINT]\",\n",
        "    api_key=\"[API-KEY]\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "724eae29",
      "metadata": {},
      "source": [
        "## Building the exposures table\n",
        "\n",
        "We need a value per asset per date, on the assets and the trading days of the dataset we are going to model. The cheapest way to get that grid is to ask the exposures API for the dataset's own style exposures: the first column is the date, the second is the asset id, and the rest are the served style factors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "6c28317c",
      "metadata": {},
      "outputs": [],
      "source": [
        "dataset = \"bayesline/Bayesline-US-500-1y\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "44ccd7ce",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 9)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>bayesid</th><th>style.Dividend</th><th>style.Growth</th><th>style.Leverage</th><th>style.Momentum</th><th>style.Size</th><th>style.Value</th><th>style.Volatility</th></tr><tr><td>date</td><td>str</td><td>f32</td><td>f32</td><td>f32</td><td>f32</td><td>f32</td><td>f32</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>&quot;IC006CA2E0&quot;</td><td>0.389404</td><td>0.768555</td><td>0.261597</td><td>0.844238</td><td>2.708984</td><td>-0.600098</td><td>-0.182373</td></tr><tr><td>2025-03-31</td><td>&quot;IC0121F541&quot;</td><td>0.027115</td><td>1.125</td><td>2.035645</td><td>0.84082</td><td>1.805176</td><td>-1.351562</td><td>-0.601644</td></tr><tr><td>2025-03-31</td><td>&quot;IC012D373B&quot;</td><td>0.121216</td><td>0.556641</td><td>0.988037</td><td>-0.605957</td><td>2.166504</td><td>-0.881836</td><td>0.357689</td></tr><tr><td>2025-03-31</td><td>&quot;IC01430182&quot;</td><td>-0.183716</td><td>0.878906</td><td>-0.552444</td><td>0.049316</td><td>1.960449</td><td>-1.151367</td><td>0.42863</td></tr><tr><td>2025-03-31</td><td>&quot;IC015A481B&quot;</td><td>-0.138794</td><td>1.1640625</td><td>0.22403</td><td>-0.615723</td><td>2.044434</td><td>-0.647461</td><td>-0.062337</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 9)\n",
              "┌───────────┬───────────┬───────────┬───────────┬───┬───────────┬───────────┬───────────┬──────────┐\n",
              "│ date      ┆ bayesid   ┆ style.Div ┆ style.Gro ┆ … ┆ style.Mom ┆ style.Siz ┆ style.Val ┆ style.Vo │\n",
              "│ ---       ┆ ---       ┆ idend     ┆ wth       ┆   ┆ entum     ┆ e         ┆ ue        ┆ latility │\n",
              "│ date      ┆ str       ┆ ---       ┆ ---       ┆   ┆ ---       ┆ ---       ┆ ---       ┆ ---      │\n",
              "│           ┆           ┆ f32       ┆ f32       ┆   ┆ f32       ┆ f32       ┆ f32       ┆ f32      │\n",
              "╞═══════════╪═══════════╪═══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪══════════╡\n",
              "│ 2025-03-3 ┆ IC006CA2E ┆ 0.389404  ┆ 0.768555  ┆ … ┆ 0.844238  ┆ 2.708984  ┆ -0.600098 ┆ -0.18237 │\n",
              "│ 1         ┆ 0         ┆           ┆           ┆   ┆           ┆           ┆           ┆ 3        │\n",
              "│ 2025-03-3 ┆ IC0121F54 ┆ 0.027115  ┆ 1.125     ┆ … ┆ 0.84082   ┆ 1.805176  ┆ -1.351562 ┆ -0.60164 │\n",
              "│ 1         ┆ 1         ┆           ┆           ┆   ┆           ┆           ┆           ┆ 4        │\n",
              "│ 2025-03-3 ┆ IC012D373 ┆ 0.121216  ┆ 0.556641  ┆ … ┆ -0.605957 ┆ 2.166504  ┆ -0.881836 ┆ 0.357689 │\n",
              "│ 1         ┆ B         ┆           ┆           ┆   ┆           ┆           ┆           ┆          │\n",
              "│ 2025-03-3 ┆ IC0143018 ┆ -0.183716 ┆ 0.878906  ┆ … ┆ 0.049316  ┆ 1.960449  ┆ -1.151367 ┆ 0.42863  │\n",
              "│ 1         ┆ 2         ┆           ┆           ┆   ┆           ┆           ┆           ┆          │\n",
              "│ 2025-03-3 ┆ IC015A481 ┆ -0.138794 ┆ 1.1640625 ┆ … ┆ -0.615723 ┆ 2.044434  ┆ -0.647461 ┆ -0.06233 │\n",
              "│ 1         ┆ B         ┆           ┆           ┆   ┆           ┆           ┆           ┆ 7        │\n",
              "└───────────┴───────────┴───────────┴───────────┴───┴───────────┴───────────┴───────────┴──────────┘"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "exposures_api = bln.equity.exposures.load(\n",
        "    ExposureSettings(\n",
        "        exposures=[ContinuousExposureGroupSettings(hierarchy=\"style\")]\n",
        "    ).with_dataset(dataset)\n",
        ")\n",
        "\n",
        "style_df = exposures_api.get(\n",
        "    UniverseSettings(), standardize_universe=None, filter_tradedays=True\n",
        ")\n",
        "style_df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "37481834",
      "metadata": {},
      "source": [
        "We build two factors on that grid, so the notebook stays reproducible without any outside data:\n",
        "\n",
        "* `value_x_momentum` is the product of the served Value and Momentum style exposures. It is deterministic and it is not a linear combination of the style block, so it survives next to it in the same regression.\n",
        "* `random_signal` is a seeded draw from a standard normal, standing in for a proprietary signal.\n",
        "\n",
        "The uploader takes the long format: one row per `date`, `asset_id`, `asset_id_type`, `factor_group`, `factor`, with the value in `exposure`. Those six columns are the primary key plus the value, and anything else we hand it is dropped. We upload `bayesid` ids because that is what the exposures API served us."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "031eeb47",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 6)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>asset_id</th><th>asset_id_type</th><th>factor_group</th><th>factor</th><th>exposure</th></tr><tr><td>date</td><td>str</td><td>str</td><td>str</td><td>str</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>&quot;IC006CA2E0&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>-0.506625</td></tr><tr><td>2025-03-31</td><td>&quot;IC0121F541&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>-1.136421</td></tr><tr><td>2025-03-31</td><td>&quot;IC012D373B&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>0.534355</td></tr><tr><td>2025-03-31</td><td>&quot;IC01430182&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>-0.056781</td></tr><tr><td>2025-03-31</td><td>&quot;IC015A481B&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>0.398656</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 6)\n",
              "┌────────────┬────────────┬───────────────┬──────────────┬──────────────────┬───────────┐\n",
              "│ date       ┆ asset_id   ┆ asset_id_type ┆ factor_group ┆ factor           ┆ exposure  │\n",
              "│ ---        ┆ ---        ┆ ---           ┆ ---          ┆ ---              ┆ ---       │\n",
              "│ date       ┆ str        ┆ str           ┆ str          ┆ str              ┆ f32       │\n",
              "╞════════════╪════════════╪═══════════════╪══════════════╪══════════════════╪═══════════╡\n",
              "│ 2025-03-31 ┆ IC006CA2E0 ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ -0.506625 │\n",
              "│ 2025-03-31 ┆ IC0121F541 ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ -1.136421 │\n",
              "│ 2025-03-31 ┆ IC012D373B ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ 0.534355  │\n",
              "│ 2025-03-31 ┆ IC01430182 ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ -0.056781 │\n",
              "│ 2025-03-31 ┆ IC015A481B ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ 0.398656  │\n",
              "└────────────┴────────────┴───────────────┴──────────────┴──────────────────┴───────────┘"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "interaction_df = style_df.select(\n",
        "    \"date\",\n",
        "    pl.col(\"bayesid\").alias(\"asset_id\"),\n",
        "    pl.lit(\"bayesid\").alias(\"asset_id_type\"),\n",
        "    pl.lit(\"my_signals\").alias(\"factor_group\"),\n",
        "    pl.lit(\"value_x_momentum\").alias(\"factor\"),\n",
        "    (pl.col(\"style.Value\") * pl.col(\"style.Momentum\")).cast(pl.Float32).alias(\"exposure\"),\n",
        ")\n",
        "interaction_df.head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "194a2750",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 6)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>asset_id</th><th>asset_id_type</th><th>factor_group</th><th>factor</th><th>exposure</th></tr><tr><td>date</td><td>str</td><td>str</td><td>str</td><td>str</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>&quot;IC006CA2E0&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;random_signal&quot;</td><td>0.304717</td></tr><tr><td>2025-03-31</td><td>&quot;IC0121F541&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;random_signal&quot;</td><td>-1.039984</td></tr><tr><td>2025-03-31</td><td>&quot;IC012D373B&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;random_signal&quot;</td><td>0.750451</td></tr><tr><td>2025-03-31</td><td>&quot;IC01430182&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;random_signal&quot;</td><td>0.940565</td></tr><tr><td>2025-03-31</td><td>&quot;IC015A481B&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;random_signal&quot;</td><td>-1.951035</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 6)\n",
              "┌────────────┬────────────┬───────────────┬──────────────┬───────────────┬───────────┐\n",
              "│ date       ┆ asset_id   ┆ asset_id_type ┆ factor_group ┆ factor        ┆ exposure  │\n",
              "│ ---        ┆ ---        ┆ ---           ┆ ---          ┆ ---           ┆ ---       │\n",
              "│ date       ┆ str        ┆ str           ┆ str          ┆ str           ┆ f32       │\n",
              "╞════════════╪════════════╪═══════════════╪══════════════╪═══════════════╪═══════════╡\n",
              "│ 2025-03-31 ┆ IC006CA2E0 ┆ bayesid       ┆ my_signals   ┆ random_signal ┆ 0.304717  │\n",
              "│ 2025-03-31 ┆ IC0121F541 ┆ bayesid       ┆ my_signals   ┆ random_signal ┆ -1.039984 │\n",
              "│ 2025-03-31 ┆ IC012D373B ┆ bayesid       ┆ my_signals   ┆ random_signal ┆ 0.750451  │\n",
              "│ 2025-03-31 ┆ IC01430182 ┆ bayesid       ┆ my_signals   ┆ random_signal ┆ 0.940565  │\n",
              "│ 2025-03-31 ┆ IC015A481B ┆ bayesid       ┆ my_signals   ┆ random_signal ┆ -1.951035 │\n",
              "└────────────┴────────────┴───────────────┴──────────────┴───────────────┴───────────┘"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "rng = np.random.default_rng(42)\n",
        "\n",
        "random_df = style_df.select(\n",
        "    \"date\",\n",
        "    pl.col(\"bayesid\").alias(\"asset_id\"),\n",
        "    pl.lit(\"bayesid\").alias(\"asset_id_type\"),\n",
        "    pl.lit(\"my_signals\").alias(\"factor_group\"),\n",
        "    pl.lit(\"random_signal\").alias(\"factor\"),\n",
        "    pl.Series(\"exposure\", rng.standard_normal(style_df.height), dtype=pl.Float32),\n",
        ")\n",
        "random_df.head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "b3ab694a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 6)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>asset_id</th><th>asset_id_type</th><th>factor_group</th><th>factor</th><th>exposure</th></tr><tr><td>date</td><td>str</td><td>str</td><td>str</td><td>str</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>&quot;IC006CA2E0&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>-0.506625</td></tr><tr><td>2025-03-31</td><td>&quot;IC0121F541&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>-1.136421</td></tr><tr><td>2025-03-31</td><td>&quot;IC012D373B&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>0.534355</td></tr><tr><td>2025-03-31</td><td>&quot;IC01430182&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>-0.056781</td></tr><tr><td>2025-03-31</td><td>&quot;IC015A481B&quot;</td><td>&quot;bayesid&quot;</td><td>&quot;my_signals&quot;</td><td>&quot;value_x_momentum&quot;</td><td>0.398656</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 6)\n",
              "┌────────────┬────────────┬───────────────┬──────────────┬──────────────────┬───────────┐\n",
              "│ date       ┆ asset_id   ┆ asset_id_type ┆ factor_group ┆ factor           ┆ exposure  │\n",
              "│ ---        ┆ ---        ┆ ---           ┆ ---          ┆ ---              ┆ ---       │\n",
              "│ date       ┆ str        ┆ str           ┆ str          ┆ str              ┆ f32       │\n",
              "╞════════════╪════════════╪═══════════════╪══════════════╪══════════════════╪═══════════╡\n",
              "│ 2025-03-31 ┆ IC006CA2E0 ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ -0.506625 │\n",
              "│ 2025-03-31 ┆ IC0121F541 ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ -1.136421 │\n",
              "│ 2025-03-31 ┆ IC012D373B ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ 0.534355  │\n",
              "│ 2025-03-31 ┆ IC01430182 ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ -0.056781 │\n",
              "│ 2025-03-31 ┆ IC015A481B ┆ bayesid       ┆ my_signals   ┆ value_x_momentum ┆ 0.398656  │\n",
              "└────────────┴────────────┴───────────────┴──────────────┴──────────────────┴───────────┘"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "upload_df = pl.concat([interaction_df, random_df])\n",
        "upload_df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7434da7f",
      "metadata": {},
      "source": [
        "## Uploading\n",
        "\n",
        "Both factors go into one `exposures` upload under one `factor_group`, which is the unit an inline group reads. `fast_commit` skips the staging step, which is what we want for a dataframe we already hold in memory. See the [Uploaders Tutorial](https://docs.bayesline.com/0.21.0/notebooks/tutorial_uploaders.html) for staging and for the other commit modes."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "993f4974",
      "metadata": {},
      "outputs": [],
      "source": [
        "exposure_dataset_name = \"My-Inline-Signals\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "1441a293",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "UploadCommitResult(version=1, committed_names=[])"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "exposure_uploaders = bln.equity.uploaders.get_data_type(\"exposures\")\n",
        "my_signals = exposure_uploaders.create_or_replace_dataset(exposure_dataset_name)\n",
        "\n",
        "my_signals.fast_commit(upload_df, mode=\"append\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "6a233d2d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "upload: My-Inline-Signals\n",
            "rows: 250706\n",
            "dates: 2025-03-31 to 2026-03-31\n"
          ]
        }
      ],
      "source": [
        "committed_df = my_signals.get_data().collect()\n",
        "\n",
        "print(\"upload:\", exposure_dataset_name)\n",
        "print(\"rows:\", committed_df.height)\n",
        "print(\"dates:\", committed_df[\"date\"].min(), \"to\", committed_df[\"date\"].max())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "2823d508",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (252, 6)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>n_assets</th><th>min_exposure</th><th>max_exposure</th><th>mean_exposure</th><th>std_exposure</th></tr><tr><td>date</td><td>i64</td><td>f32</td><td>f32</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>2025-03-31</td><td>500</td><td>-3.3125</td><td>2.9140625</td><td>-0.1275</td><td>0.897733</td></tr><tr><td>2025-04-01</td><td>500</td><td>-3.648438</td><td>3.1796875</td><td>-0.133088</td><td>0.922186</td></tr><tr><td>2025-04-02</td><td>500</td><td>-3.322266</td><td>2.808594</td><td>-0.093945</td><td>0.919222</td></tr><tr><td>2025-04-03</td><td>500</td><td>-3.248047</td><td>2.9140625</td><td>-0.175863</td><td>0.898337</td></tr><tr><td>2025-04-04</td><td>500</td><td>-3.064453</td><td>2.857422</td><td>-0.085856</td><td>0.896702</td></tr><tr><td>&hellip;</td><td>&hellip;</td><td>&hellip;</td><td>&hellip;</td><td>&hellip;</td><td>&hellip;</td></tr><tr><td>2026-03-25</td><td>496</td><td>-3.105469</td><td>3.552734</td><td>0.061672</td><td>0.988378</td></tr><tr><td>2026-03-26</td><td>496</td><td>-3.160156</td><td>3.707031</td><td>0.060841</td><td>0.981063</td></tr><tr><td>2026-03-27</td><td>496</td><td>-3.427734</td><td>3.503906</td><td>0.058309</td><td>0.963608</td></tr><tr><td>2026-03-30</td><td>496</td><td>-3.041016</td><td>3.505859</td><td>0.098044</td><td>0.938905</td></tr><tr><td>2026-03-31</td><td>496</td><td>-2.851562</td><td>3.472656</td><td>0.065479</td><td>0.94985</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (252, 6)\n",
              "┌────────────┬──────────┬──────────────┬──────────────┬───────────────┬──────────────┐\n",
              "│ date       ┆ n_assets ┆ min_exposure ┆ max_exposure ┆ mean_exposure ┆ std_exposure │\n",
              "│ ---        ┆ ---      ┆ ---          ┆ ---          ┆ ---           ┆ ---          │\n",
              "│ date       ┆ i64      ┆ f32          ┆ f32          ┆ f64           ┆ f64          │\n",
              "╞════════════╪══════════╪══════════════╪══════════════╪═══════════════╪══════════════╡\n",
              "│ 2025-03-31 ┆ 500      ┆ -3.3125      ┆ 2.9140625    ┆ -0.1275       ┆ 0.897733     │\n",
              "│ 2025-04-01 ┆ 500      ┆ -3.648438    ┆ 3.1796875    ┆ -0.133088     ┆ 0.922186     │\n",
              "│ 2025-04-02 ┆ 500      ┆ -3.322266    ┆ 2.808594     ┆ -0.093945     ┆ 0.919222     │\n",
              "│ 2025-04-03 ┆ 500      ┆ -3.248047    ┆ 2.9140625    ┆ -0.175863     ┆ 0.898337     │\n",
              "│ 2025-04-04 ┆ 500      ┆ -3.064453    ┆ 2.857422     ┆ -0.085856     ┆ 0.896702     │\n",
              "│ …          ┆ …        ┆ …            ┆ …            ┆ …             ┆ …            │\n",
              "│ 2026-03-25 ┆ 496      ┆ -3.105469    ┆ 3.552734     ┆ 0.061672      ┆ 0.988378     │\n",
              "│ 2026-03-26 ┆ 496      ┆ -3.160156    ┆ 3.707031     ┆ 0.060841      ┆ 0.981063     │\n",
              "│ 2026-03-27 ┆ 496      ┆ -3.427734    ┆ 3.503906     ┆ 0.058309      ┆ 0.963608     │\n",
              "│ 2026-03-30 ┆ 496      ┆ -3.041016    ┆ 3.505859     ┆ 0.098044      ┆ 0.938905     │\n",
              "│ 2026-03-31 ┆ 496      ┆ -2.851562    ┆ 3.472656     ┆ 0.065479      ┆ 0.94985      │\n",
              "└────────────┴──────────┴──────────────┴──────────────┴───────────────┴──────────────┘"
            ]
          },
          "execution_count": 11,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "my_signals.get_data_detail_summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7c10510a",
      "metadata": {},
      "source": [
        "## Referencing the upload from a model\n",
        "\n",
        "`InlineUploadedExposureGroupSettings` names the upload and the factor group inside it, and sits in `ExposureSettings.exposures` next to the dataset's own groups. Everything else about the model is standard.\n",
        "\n",
        "The group's knobs are all off by default, on the assumption that the values you uploaded are the values you want used.\n",
        "\n",
        "* `forward_fill` carries the last known value across dates the upload has no row for, gated by the days the asset is in the modeling universe.\n",
        "* `gaussianize` converts the values to standard-normal ranks; `gaussianize_maintain_zeros` keeps zeros at zero while doing so.\n",
        "* `fill_miss` fills in missing exposures.\n",
        "* `standardize_method=\"equal_weighted\"` z-scores the values per date against the mean and standard deviation of the estimation universe; `\"none\"` passes them through untouched.\n",
        "\n",
        "Assets in the upload that are not part of the risk dataset are dropped with a warning when the report is built, rather than failing the run."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "19ce36d8",
      "metadata": {},
      "outputs": [],
      "source": [
        "factorriskmodel_settings = FactorRiskModelSettings(\n",
        "    universe=UniverseSettings(),\n",
        "    exposures=ExposureSettings(\n",
        "        exposures=[\n",
        "            ContinuousExposureGroupSettings(hierarchy=\"market\"),\n",
        "            CategoricalExposureGroupSettings(hierarchy=\"trbc\"),\n",
        "            ContinuousExposureGroupSettings(hierarchy=\"style\"),\n",
        "            InlineUploadedExposureGroupSettings(\n",
        "                exposure_source=exposure_dataset_name,\n",
        "                factor_group=\"my_signals\",\n",
        "                standardize_method=\"none\",\n",
        "            ),\n",
        "        ]\n",
        "    ),\n",
        "    modelconstruction=ModelConstructionSettings(\n",
        "        weights=\"InvIdioVar\",\n",
        "        estimation_universe=UniverseSettings(\n",
        "            categorical_filters=[\n",
        "                CategoricalFilterSettings(hierarchy=\"estimation_universe\")\n",
        "            ],\n",
        "        ),\n",
        "        return_clip_bounds=(None, None),\n",
        "        zero_sum_constraints={\"trbc\": \"mcap_weighted\"},\n",
        "    ),\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "71a2179a",
      "metadata": {},
      "outputs": [],
      "source": [
        "risk_model = bln.equity.riskmodels.load(\n",
        "    factorriskmodel_settings.with_dataset(dataset)\n",
        ").get_model()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6cec010e",
      "metadata": {},
      "source": [
        "## The group behaves like any other dense group\n",
        "\n",
        "The upload's factor group shows up alongside the dataset's groups, and its factors are named `my_signals.value_x_momentum` and `my_signals.random_signal` after the `factor_group` value in the upload. Being dense, the group can also be a target or source of a `net_of` projection; the `tutorial_exposure_orthogonalization` notebook covers that in full."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "d4cc59dd",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'market': ['Market'],\n",
              " 'my_signals': ['random_signal', 'value_x_momentum'],\n",
              " 'trbc': ['Academic & Educational Services',\n",
              "  'Basic Materials',\n",
              "  'Consumer Cyclicals',\n",
              "  'Consumer Non-Cyclicals',\n",
              "  'Energy',\n",
              "  'Financials',\n",
              "  'Government Activity',\n",
              "  'Healthcare',\n",
              "  'Industrials',\n",
              "  'Institutions, Associations & Organizations',\n",
              "  'Real Estate',\n",
              "  'Technology',\n",
              "  'Utilities'],\n",
              " 'style': ['Dividend',\n",
              "  'Growth',\n",
              "  'Leverage',\n",
              "  'Momentum',\n",
              "  'Size',\n",
              "  'Value',\n",
              "  'Volatility']}"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "risk_model.factors()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "4a09a3a7",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 5)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>bayesid</th><th>market.Market</th><th>my_signals.value_x_momentum</th><th>my_signals.random_signal</th></tr><tr><td>date</td><td>str</td><td>f32</td><td>f32</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>&quot;IC006CA2E0&quot;</td><td>1.0</td><td>-0.506836</td><td>0.3046875</td></tr><tr><td>2025-03-31</td><td>&quot;IC0121F541&quot;</td><td>1.0</td><td>-1.136719</td><td>-1.040039</td></tr><tr><td>2025-03-31</td><td>&quot;IC012D373B&quot;</td><td>1.0</td><td>0.53418</td><td>0.750488</td></tr><tr><td>2025-03-31</td><td>&quot;IC01430182&quot;</td><td>1.0</td><td>-0.056793</td><td>0.94043</td></tr><tr><td>2025-03-31</td><td>&quot;IC015A481B&quot;</td><td>1.0</td><td>0.398682</td><td>-1.951172</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 5)\n",
              "┌────────────┬────────────┬───────────────┬─────────────────────────────┬──────────────────────────┐\n",
              "│ date       ┆ bayesid    ┆ market.Market ┆ my_signals.value_x_momentum ┆ my_signals.random_signal │\n",
              "│ ---        ┆ ---        ┆ ---           ┆ ---                         ┆ ---                      │\n",
              "│ date       ┆ str        ┆ f32           ┆ f32                         ┆ f32                      │\n",
              "╞════════════╪════════════╪═══════════════╪═════════════════════════════╪══════════════════════════╡\n",
              "│ 2025-03-31 ┆ IC006CA2E0 ┆ 1.0           ┆ -0.506836                   ┆ 0.3046875                │\n",
              "│ 2025-03-31 ┆ IC0121F541 ┆ 1.0           ┆ -1.136719                   ┆ -1.040039                │\n",
              "│ 2025-03-31 ┆ IC012D373B ┆ 1.0           ┆ 0.53418                     ┆ 0.750488                 │\n",
              "│ 2025-03-31 ┆ IC01430182 ┆ 1.0           ┆ -0.056793                   ┆ 0.94043                  │\n",
              "│ 2025-03-31 ┆ IC015A481B ┆ 1.0           ┆ 0.398682                    ┆ -1.951172                │\n",
              "└────────────┴────────────┴───────────────┴─────────────────────────────┴──────────────────────────┘"
            ]
          },
          "execution_count": 15,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "risk_model.exposures().select(\n",
        "    \"date\",\n",
        "    \"bayesid\",\n",
        "    \"market.Market\",\n",
        "    \"my_signals.value_x_momentum\",\n",
        "    \"my_signals.random_signal\",\n",
        ").head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "4308e878",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>market.Market</th><th>my_signals.value_x_momentum</th><th>my_signals.random_signal</th></tr><tr><td>date</td><td>f32</td><td>f32</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>0.0</td><td>0.0</td><td>0.0</td></tr><tr><td>2025-04-01</td><td>0.007404</td><td>0.00077</td><td>-0.000646</td></tr><tr><td>2025-04-02</td><td>0.02212</td><td>0.001807</td><td>-0.000461</td></tr><tr><td>2025-04-03</td><td>-0.075951</td><td>-0.002974</td><td>0.003802</td></tr><tr><td>2025-04-04</td><td>-0.030753</td><td>0.005609</td><td>-0.001347</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 4)\n",
              "┌────────────┬───────────────┬─────────────────────────────┬──────────────────────────┐\n",
              "│ date       ┆ market.Market ┆ my_signals.value_x_momentum ┆ my_signals.random_signal │\n",
              "│ ---        ┆ ---           ┆ ---                         ┆ ---                      │\n",
              "│ date       ┆ f32           ┆ f32                         ┆ f32                      │\n",
              "╞════════════╪═══════════════╪═════════════════════════════╪══════════════════════════╡\n",
              "│ 2025-03-31 ┆ 0.0           ┆ 0.0                         ┆ 0.0                      │\n",
              "│ 2025-04-01 ┆ 0.007404      ┆ 0.00077                     ┆ -0.000646                │\n",
              "│ 2025-04-02 ┆ 0.02212       ┆ 0.001807                    ┆ -0.000461                │\n",
              "│ 2025-04-03 ┆ -0.075951     ┆ -0.002974                   ┆ 0.003802                 │\n",
              "│ 2025-04-04 ┆ -0.030753     ┆ 0.005609                    ┆ -0.001347                │\n",
              "└────────────┴───────────────┴─────────────────────────────┴──────────────────────────┘"
            ]
          },
          "execution_count": 16,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "risk_model.fret().select(\n",
        "    \"date\",\n",
        "    \"market.Market\",\n",
        "    \"my_signals.value_x_momentum\",\n",
        "    \"my_signals.random_signal\",\n",
        ").head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "e2dd345f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>market.Market</th><th>my_signals.value_x_momentum</th><th>my_signals.random_signal</th></tr><tr><td>date</td><td>f32</td><td>f32</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>NaN</td><td>NaN</td><td>NaN</td></tr><tr><td>2025-04-01</td><td>2.433605</td><td>0.841934</td><td>-1.57795</td></tr><tr><td>2025-04-02</td><td>7.822511</td><td>2.10416</td><td>-1.582667</td></tr><tr><td>2025-04-03</td><td>-7.677681</td><td>-1.007727</td><td>3.671594</td></tr><tr><td>2025-04-04</td><td>-4.016456</td><td>2.335344</td><td>-1.626977</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 4)\n",
              "┌────────────┬───────────────┬─────────────────────────────┬──────────────────────────┐\n",
              "│ date       ┆ market.Market ┆ my_signals.value_x_momentum ┆ my_signals.random_signal │\n",
              "│ ---        ┆ ---           ┆ ---                         ┆ ---                      │\n",
              "│ date       ┆ f32           ┆ f32                         ┆ f32                      │\n",
              "╞════════════╪═══════════════╪═════════════════════════════╪══════════════════════════╡\n",
              "│ 2025-03-31 ┆ NaN           ┆ NaN                         ┆ NaN                      │\n",
              "│ 2025-04-01 ┆ 2.433605      ┆ 0.841934                    ┆ -1.57795                 │\n",
              "│ 2025-04-02 ┆ 7.822511      ┆ 2.10416                     ┆ -1.582667                │\n",
              "│ 2025-04-03 ┆ -7.677681     ┆ -1.007727                   ┆ 3.671594                 │\n",
              "│ 2025-04-04 ┆ -4.016456     ┆ 2.335344                    ┆ -1.626977                │\n",
              "└────────────┴───────────────┴─────────────────────────────┴──────────────────────────┘"
            ]
          },
          "execution_count": 17,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "risk_model.t_stats().select(\n",
        "    \"date\",\n",
        "    \"market.Market\",\n",
        "    \"my_signals.value_x_momentum\",\n",
        "    \"my_signals.random_signal\",\n",
        ").head()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "550d7c30",
      "metadata": {},
      "source": [
        "## Checking the served exposures against the upload\n",
        "\n",
        "With `standardize_method=\"none\"` and every other knob off, the served exposures are the values we uploaded."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "b2ed9f1c",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>asset_id</th><th>value_x_momentum</th><th>random_signal</th></tr><tr><td>date</td><td>str</td><td>f32</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>&quot;IC006CA2E0&quot;</td><td>-0.506625</td><td>0.304717</td></tr><tr><td>2025-03-31</td><td>&quot;IC0121F541&quot;</td><td>-1.136421</td><td>-1.039984</td></tr><tr><td>2025-03-31</td><td>&quot;IC012D373B&quot;</td><td>0.534355</td><td>0.750451</td></tr><tr><td>2025-03-31</td><td>&quot;IC01430182&quot;</td><td>-0.056781</td><td>0.940565</td></tr><tr><td>2025-03-31</td><td>&quot;IC015A481B&quot;</td><td>0.398656</td><td>-1.951035</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 4)\n",
              "┌────────────┬────────────┬──────────────────┬───────────────┐\n",
              "│ date       ┆ asset_id   ┆ value_x_momentum ┆ random_signal │\n",
              "│ ---        ┆ ---        ┆ ---              ┆ ---           │\n",
              "│ date       ┆ str        ┆ f32              ┆ f32           │\n",
              "╞════════════╪════════════╪══════════════════╪═══════════════╡\n",
              "│ 2025-03-31 ┆ IC006CA2E0 ┆ -0.506625        ┆ 0.304717      │\n",
              "│ 2025-03-31 ┆ IC0121F541 ┆ -1.136421        ┆ -1.039984     │\n",
              "│ 2025-03-31 ┆ IC012D373B ┆ 0.534355         ┆ 0.750451      │\n",
              "│ 2025-03-31 ┆ IC01430182 ┆ -0.056781        ┆ 0.940565      │\n",
              "│ 2025-03-31 ┆ IC015A481B ┆ 0.398656         ┆ -1.951035     │\n",
              "└────────────┴────────────┴──────────────────┴───────────────┘"
            ]
          },
          "execution_count": 18,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "uploaded_wide = upload_df.pivot(\"factor\", index=[\"date\", \"asset_id\"], values=\"exposure\")\n",
        "uploaded_wide.head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "a8591a75",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/html": [
              "<div><style>\n",
              ".dataframe > thead > tr,\n",
              ".dataframe > tbody > tr {\n",
              "  text-align: right;\n",
              "  white-space: pre-wrap;\n",
              "}\n",
              "</style>\n",
              "<small>shape: (5, 6)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>date</th><th>bayesid</th><th>my_signals.value_x_momentum</th><th>my_signals.random_signal</th><th>value_x_momentum</th><th>random_signal</th></tr><tr><td>date</td><td>str</td><td>f32</td><td>f32</td><td>f32</td><td>f32</td></tr></thead><tbody><tr><td>2025-03-31</td><td>&quot;IC006CA2E0&quot;</td><td>-0.506836</td><td>0.3046875</td><td>-0.506625</td><td>0.304717</td></tr><tr><td>2025-03-31</td><td>&quot;IC0121F541&quot;</td><td>-1.136719</td><td>-1.040039</td><td>-1.136421</td><td>-1.039984</td></tr><tr><td>2025-03-31</td><td>&quot;IC012D373B&quot;</td><td>0.53418</td><td>0.750488</td><td>0.534355</td><td>0.750451</td></tr><tr><td>2025-03-31</td><td>&quot;IC01430182&quot;</td><td>-0.056793</td><td>0.94043</td><td>-0.056781</td><td>0.940565</td></tr><tr><td>2025-03-31</td><td>&quot;IC015A481B&quot;</td><td>0.398682</td><td>-1.951172</td><td>0.398656</td><td>-1.951035</td></tr></tbody></table></div>"
            ],
            "text/plain": [
              "shape: (5, 6)\n",
              "┌────────────┬────────────┬──────────────────┬──────────────────┬──────────────────┬───────────────┐\n",
              "│ date       ┆ bayesid    ┆ my_signals.value ┆ my_signals.rando ┆ value_x_momentum ┆ random_signal │\n",
              "│ ---        ┆ ---        ┆ _x_momentum      ┆ m_signal         ┆ ---              ┆ ---           │\n",
              "│ date       ┆ str        ┆ ---              ┆ ---              ┆ f32              ┆ f32           │\n",
              "│            ┆            ┆ f32              ┆ f32              ┆                  ┆               │\n",
              "╞════════════╪════════════╪══════════════════╪══════════════════╪══════════════════╪═══════════════╡\n",
              "│ 2025-03-31 ┆ IC006CA2E0 ┆ -0.506836        ┆ 0.3046875        ┆ -0.506625        ┆ 0.304717      │\n",
              "│ 2025-03-31 ┆ IC0121F541 ┆ -1.136719        ┆ -1.040039        ┆ -1.136421        ┆ -1.039984     │\n",
              "│ 2025-03-31 ┆ IC012D373B ┆ 0.53418          ┆ 0.750488         ┆ 0.534355         ┆ 0.750451      │\n",
              "│ 2025-03-31 ┆ IC01430182 ┆ -0.056793        ┆ 0.94043          ┆ -0.056781        ┆ 0.940565      │\n",
              "│ 2025-03-31 ┆ IC015A481B ┆ 0.398682         ┆ -1.951172        ┆ 0.398656         ┆ -1.951035     │\n",
              "└────────────┴────────────┴──────────────────┴──────────────────┴──────────────────┴───────────────┘"
            ]
          },
          "execution_count": 19,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "check_df = risk_model.exposures().select(\n",
        "    \"date\", \"bayesid\", \"my_signals.value_x_momentum\", \"my_signals.random_signal\"\n",
        ").join(\n",
        "    uploaded_wide,\n",
        "    left_on=[\"date\", \"bayesid\"],\n",
        "    right_on=[\"date\", \"asset_id\"],\n",
        "    how=\"left\",\n",
        ")\n",
        "check_df.head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "c6f106a5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "served rows: 125353\n",
            "served rows the upload does not cover (== 0): 0\n",
            "max |served - uploaded| value_x_momentum (~= 0): 0.0009765625\n",
            "max |served - uploaded| random_signal (~= 0): 0.0019207000732421875\n"
          ]
        }
      ],
      "source": [
        "max_diff_interaction = (\n",
        "    check_df[\"my_signals.value_x_momentum\"] - check_df[\"value_x_momentum\"]\n",
        ").abs().max()\n",
        "\n",
        "max_diff_random = (\n",
        "    check_df[\"my_signals.random_signal\"] - check_df[\"random_signal\"]\n",
        ").abs().max()\n",
        "\n",
        "print(\"served rows:\", check_df.height)\n",
        "print(\"served rows the upload does not cover (== 0):\", check_df[\"value_x_momentum\"].null_count())\n",
        "print(\"max |served - uploaded| value_x_momentum (~= 0):\", max_diff_interaction)\n",
        "print(\"max |served - uploaded| random_signal (~= 0):\", max_diff_random)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "f1bbd9d8",
      "metadata": {},
      "outputs": [],
      "source": [
        "assert check_df[\"value_x_momentum\"].null_count() == 0\n",
        "assert max_diff_interaction < 0.05\n",
        "assert max_diff_random < 0.05"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b87c4a9a",
      "metadata": {},
      "source": [
        "## Housekeeping"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "01bc4dff",
      "metadata": {},
      "outputs": [],
      "source": [
        "my_signals.destroy()"
      ]
    }
  ],
  "metadata": {
    "jupytext": {
      "text_representation": {
        "extension": ".py",
        "format_name": "percent",
        "format_version": "1.3",
        "jupytext_version": "1.19.3"
      }
    },
    "kernelspec": {
      "display_name": ".venv",
      "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.15"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}