{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "29f7e620-7b04-45f6-ac87-f17505f140c3", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import plotly.graph_objects as go\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 6, "id": "a48ad016-e4f2-40d9-a607-344a316f5f02", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\n", "KeyboardInterrupt\n", "\n" ] } ], "source": [ "stocks = pd.read_csv(\"stocks.csv\")\n", "flows = pd.read_csv(\"flows.csv\")\n", "\n", "stocks[\"Centralisation Date\"] = pd.to_datetime(stocks[\"Centralisation Date\"])\n", "flows[\"Centralisation Date\"] = pd.to_datetime(flows[\"Centralisation Date\"])" ] }, { "cell_type": "code", "execution_count": 11, "id": "221a4c7b-0f50-431a-875b-ad40bed7f0ac", "metadata": {}, "outputs": [], "source": [ "import os\n", "import s3fs\n", "os.environ[\"AWS_ACCESS_KEY_ID\"] = 'N0C5PK75FDX2WXI8OVP1'\n", "os.environ[\"AWS_SECRET_ACCESS_KEY\"] = 'nZvC2urUkG7EvhDsFDyaOslqr160aoWMs+5MP3Ft'\n", "os.environ[\"AWS_SESSION_TOKEN\"] = 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJOMEM1UEs3NUZEWDJXWEk4T1ZQMSIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJhdWQiOlsibWluaW8iLCJhY2NvdW50Il0sImF1dGhfdGltZSI6MTc3MzIyNzI3OCwiYXpwIjoib255eGlhLW1pbmlvIiwiZW1haWwiOiJzYXJhaC50aG91bXlyZUBlbnNhZS5mciIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE3NzQ0MzY4OTksImZhbWlseV9uYW1lIjoiVEhPVU1ZUkUiLCJnaXZlbl9uYW1lIjoiU2FyYWgiLCJncm91cHMiOlsiYmRjLWRhdGEiLCJiZGMtY2FybWlnbmFjLWczIl0sImlhdCI6MTc3MzIyNzI5OSwiaXNzIjoiaHR0cHM6Ly9hdXRoLmdyb3VwZS1nZW5lcy5mci9yZWFsbXMvZ2VuZXMiLCJqdGkiOiI5Mjc0ODgyMy04OTgzLTQzYjktYTZhNy0xYjhlNDdiOTRjNTUiLCJuYW1lIjoiU2FyYWggVEhPVU1ZUkUiLCJwb2xpY3kiOiJzdHNvbmx5IiwicHJlZmVycmVkX3VzZXJuYW1lIjoic3Rob3VteXJlLWVuc2FlIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1nZW5lcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6IjRkODM3NWVmLTQwY2QtNDYyMi05NzIyLTI4YjhjZTQ2MWQ5YyIsInN1YiI6ImVhYWVkN2QyLWM4MjYtNGIxNC05MzczLTYwYjNhODhlMWFiNiIsInR5cCI6IkJlYXJlciJ9.hl_SekvaH9A22PMb3W0VQBSNO67LnaneIuLC-X5XBnzOO5GLV61aocDRfYC6hvVVhdzyewSTtD2kvdyJdeu6qA'\n", "os.environ[\"AWS_DEFAULT_REGION\"] = 'us-east-1'\n", "fs = s3fs.S3FileSystem(\n", " client_kwargs={'endpoint_url': 'https://'+'minio-simple.lab.groupe-genes.fr'},\n", " key = os.environ[\"AWS_ACCESS_KEY_ID\"], \n", " secret = os.environ[\"AWS_SECRET_ACCESS_KEY\"], \n", " token = os.environ[\"AWS_SESSION_TOKEN\"])\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "87505949-ecd8-4fad-a19b-d29130be587e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Index(['Registrar Account - ID', 'Product - Isin', 'Centralisation Date',\n", " 'Quantity - AUM', 'corrected_aum', 'repair_flag'],\n", " dtype='object')\n" ] } ], "source": [ "print(stocks.columns)" ] }, { "cell_type": "code", "execution_count": null, "id": "3c6d9d05-b203-49ae-869f-7f85ead2c69e", "metadata": {}, "outputs": [], "source": [ "keys = [\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\"\n", "]\n", "\n", "stocks = stocks[keys + [\"Quantity - AUM\"]]\n", "\n", "flows = flows[keys + [\"Quantity - NetFlows\"]]\n", "\n", "flows = (\n", " flows\n", " .groupby(keys, as_index=False)\n", " .sum()\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "d30c2235-281b-41a6-828b-abb6fcfc4183", "metadata": {}, "outputs": [], "source": [ "df = stocks.merge(flows, on=keys, how=\"left\")\n", "\n", "df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", "df = df.sort_values(keys)\n", "\n", "\n", "\n", "# REBUILD ACCOUNTING IDENTITY\n", "\n", "\n", "df[\"prev_aum\"] = df.groupby(\n", " [\"Registrar Account - ID\",\"Product - Isin\"]\n", ")[\"Quantity - AUM\"].shift(1)\n", "\n", "df[\"prev_flow\"] = df.groupby(\n", " [\"Registrar Account - ID\",\"Product - Isin\"]\n", ")[\"Quantity - NetFlows\"].shift(1).fillna(0)\n", "\n", "df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"prev_flow\"]\n", "\n", "\n", "\n", "# GAP ANALYSIS\n", "\n", "\n", "df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "df[\"gap_abs\"] = df[\"gap\"].abs()\n", "\n", "EPS = 10\n", "\n", "df[\"rupture_flag\"] = (\n", " df[\"prev_aum\"].notna()\n", " & (df[\"gap_abs\"] > EPS)\n", ")\n", "\n", "\n", "\n", "# PARAMETERS\n", "\n", "\n", "GAP_TOL = 1e-6\n", "REL_GAP_THR = 0.05\n", "MIN_PERSISTENCE = 3\n", "\n", "\n", "\n", "# SORT DATA\n", "\n", "\n", "df = df.sort_values(\n", " [\"Registrar Account - ID\", \"Product - Isin\", \"Centralisation Date\"]\n", ")\n", "\n", "df[\"corrected_aum\"] = df[\"Quantity - AUM\"]\n", "df[\"repair_flag\"] = False\n", "\n", "\n", "# REBUILD EXPECTED AUM BEFORE REPAIR\n", "\n", "\n", "df = df.sort_values(\n", " [\"Registrar Account - ID\", \"Product - Isin\", \"Centralisation Date\"]\n", ")\n", "\n", "df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"prev_flow\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " .fillna(0)\n", ")\n", "\n", "df[\"expected_stock\"] = df[\"prev_aum\"] + df[\"prev_flow\"]\n", "\n", "#delete negative AUM\n", "df = df[df[\"Quantity - AUM\"] >= 0]" ] }, { "cell_type": "code", "execution_count": null, "id": "efd374d0-6393-45f2-926e-2c29249cd078", "metadata": {}, "outputs": [], "source": [ "def repair_group(g):\n", "\n", " g = g.copy()\n", "\n", " obs = g[\"Quantity - AUM\"].values\n", " flows = g[\"Quantity - NetFlows\"].values\n", "\n", " corrected = obs.copy()\n", "\n", " \n", " # Build expected AUM path\n", " \n", "\n", " expected = np.empty_like(obs)\n", " expected[0] = np.nan\n", "\n", " for t in range(1, len(obs)):\n", " expected[t] = corrected[t-1] + flows[t-1]\n", "\n", " gap = obs - expected\n", "\n", " rel_gap = np.abs(gap) / np.maximum(np.abs(expected), 1.0)\n", "\n", " idx = None\n", "\n", " \n", " # Detect persistent shift\n", " \n", "\n", " for i in range(1, len(obs) - MIN_PERSISTENCE):\n", "\n", " if (\n", " rel_gap[i] > REL_GAP_THR\n", " and np.all(np.abs(gap[i:i+MIN_PERSISTENCE] - gap[i]) < GAP_TOL)\n", " and np.all(np.abs(np.diff(flows[i:i+MIN_PERSISTENCE])) < GAP_TOL)\n", " ):\n", " idx = i\n", " break\n", "\n", " if idx is None:\n", " return g\n", "\n", " \n", " # Compute shift\n", " \n", "\n", " shift = gap[idx]\n", "\n", " candidate = obs[idx:] - shift\n", "\n", " \n", " # SAFETY CHECKS\n", " \n", "\n", " # 1. do not allow negative AUM\n", " # refuse repair if it creates NEW negative AUM\n", " if ((candidate < 0) & (obs[idx:] >= 0)).any():\n", " return g\n", "\n", " # 2. avoid extreme corrections\n", " if abs(shift) > 2 * np.nanmax(np.abs(obs)):\n", " return g\n", "\n", " \n", " # Apply correction\n", " \n", "\n", " corrected[idx:] = candidate\n", "\n", " g.loc[g.index[idx]:, \"repair_flag\"] = True\n", "\n", " \n", " # Rebuild expected path after repair\n", " \n", "\n", " expected_corr = np.empty_like(obs)\n", " expected_corr[0] = np.nan\n", "\n", " for t in range(1, len(obs)):\n", " expected_corr[t] = corrected[t-1] + flows[t-1]\n", "\n", " g[\"corrected_aum\"] = corrected\n", " g[\"expected_stock_corr\"] = expected_corr\n", "\n", " return g" ] }, { "cell_type": "code", "execution_count": null, "id": "fe1f869c-0a00-47e0-9355-3705b23561c7", "metadata": {}, "outputs": [], "source": [ "def repair_group(g):\n", "\n", " g = g.copy()\n", "\n", " obs = g[\"Quantity - AUM\"].values\n", " flows = g[\"Quantity - NetFlows\"].values\n", "\n", " corrected = obs.copy()\n", "\n", " # Build expected AUM path\n", " expected = np.empty_like(obs)\n", " expected[0] = np.nan\n", "\n", " for t in range(1, len(obs)):\n", " expected[t] = corrected[t-1] + flows[t-1]\n", "\n", " gap = obs - expected\n", " rel_gap = np.abs(gap) / np.maximum(np.abs(expected), 1.0)\n", "\n", " idx = None\n", " shift = None\n", "\n", " for i in range(1, len(obs) - MIN_PERSISTENCE - 1):\n", "\n", " # ------------------------------------------------\n", " # CASE 1 — standard persistent shift (original algo)\n", " # ------------------------------------------------\n", " if (\n", " rel_gap[i] > REL_GAP_THR\n", " and np.all(np.abs(gap[i:i+MIN_PERSISTENCE] - gap[i]) < GAP_TOL)\n", " and np.all(np.abs(np.diff(flows[i:i+MIN_PERSISTENCE])) < GAP_TOL)\n", " ):\n", " idx = i\n", " shift = gap[i]\n", " break\n", "\n", " # ------------------------------------------------\n", " # CASE 2 — double shift\n", " # ------------------------------------------------\n", " if (\n", " rel_gap[i] > REL_GAP_THR\n", " and rel_gap[i+1] > REL_GAP_THR\n", " and np.all(np.abs(gap[i+1:i+1+MIN_PERSISTENCE] - gap[i+1]) < GAP_TOL)\n", " and np.all(np.abs(np.diff(flows[i+1:i+1+MIN_PERSISTENCE])) < GAP_TOL)\n", " ):\n", " idx = i\n", " shift = gap[i+1]\n", " break\n", "\n", " if idx is None:\n", " return g\n", "\n", " # Apply shift\n", " candidate = obs[idx:] - shift\n", "\n", " # Safety checks\n", "\n", " # avoid creating new negative AUM\n", " if ((candidate < 0) & (obs[idx:] >= 0)).any():\n", " return g\n", "\n", " # avoid extreme corrections\n", " if abs(shift) > 2 * np.nanmax(np.abs(obs)):\n", " return g\n", "\n", " corrected[idx:] = candidate\n", "\n", " g.loc[g.index[idx]:, \"repair_flag\"] = True\n", "\n", " # rebuild expected path after repair\n", " expected_corr = np.empty_like(obs)\n", " expected_corr[0] = np.nan\n", "\n", " for t in range(1, len(obs)):\n", " expected_corr[t] = corrected[t-1] + flows[t-1]\n", "\n", " g[\"corrected_aum\"] = corrected\n", " g[\"expected_stock_corr\"] = expected_corr\n", "\n", " return g" ] }, { "cell_type": "code", "execution_count": 49, "id": "1ca2a5ab-354f-49af-b1aa-75c93d48de06", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_5465/3637915452.py:8: DtypeWarning:\n", "\n", "Columns (0,1,2,3) have mixed types. Specify dtype option on import or set low_memory=False.\n", "\n", "/tmp/ipykernel_5465/3637915452.py:14: DtypeWarning:\n", "\n", "Columns (0,1,2,3) have mixed types. Specify dtype option on import or set low_memory=False.\n", "\n", "/tmp/ipykernel_5465/3637915452.py:125: FutureWarning:\n", "\n", "DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n", "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "stock_repaired.csv successfully created\n" ] } ], "source": [ "#FULL STOCK REPAIR\n", "\n", "# ============================================================\n", "# LOAD DATA\n", "# ============================================================\n", "\n", "with fs.open('projet-bdc-data/carmignac/AUM ENSAE V2 -20251105.csv', 'rb') as f:\n", " stocks = pd.read_csv(f, sep=\";\")\n", "\n", "with fs.open(\n", " \"projet-bdc-data/carmignac/Flows ENSAE V2 -20251105.csv\",\n", " \"rb\"\n", ") as f:\n", " flows = pd.read_csv(f, sep=\";\")\n", "\n", "stocks[\"Centralisation Date\"] = pd.to_datetime(stocks[\"Centralisation Date\"])\n", "flows[\"Centralisation Date\"] = pd.to_datetime(flows[\"Centralisation Date\"])\n", "\n", "# ============================================================\n", "# MERGE FLOWS\n", "# ============================================================\n", "\n", "df = stocks.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", ")\n", "\n", "df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", "df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", ")\n", "\n", "# ============================================================\n", "# PARAMETERS\n", "# ============================================================\n", "\n", "REL_GAP_THR = 0.05\n", "MIN_PERSISTENCE = 3\n", "GAP_TOL = 1e-6\n", "\n", "# ============================================================\n", "# REPAIR FUNCTION\n", "# ============================================================\n", "\n", "def repair_group(g):\n", "\n", " g = g.copy()\n", "\n", " obs = g[\"Quantity - AUM\"].values\n", " flows = g[\"Quantity - NetFlows\"].values\n", "\n", " corrected = obs.copy()\n", "\n", " expected = np.empty_like(obs)\n", " expected[0] = np.nan\n", "\n", " for t in range(1, len(obs)):\n", " expected[t] = corrected[t-1] + flows[t-1]\n", "\n", " gap = obs - expected\n", " rel_gap = np.abs(gap) / np.maximum(np.abs(expected), 1.0)\n", "\n", " idx = None\n", " shift = None\n", "\n", " for i in range(1, len(obs) - MIN_PERSISTENCE - 1):\n", "\n", " # CASE 1 — persistent shift\n", " if (\n", " rel_gap[i] > REL_GAP_THR\n", " and np.all(np.abs(gap[i:i+MIN_PERSISTENCE] - gap[i]) < GAP_TOL)\n", " and np.all(np.abs(np.diff(flows[i:i+MIN_PERSISTENCE])) < GAP_TOL)\n", " ):\n", " idx = i\n", " shift = gap[i]\n", " break\n", "\n", " # CASE 2 — double shift\n", " if (\n", " rel_gap[i] > REL_GAP_THR\n", " and rel_gap[i+1] > REL_GAP_THR\n", " and np.all(np.abs(gap[i+1:i+1+MIN_PERSISTENCE] - gap[i+1]) < GAP_TOL)\n", " and np.all(np.abs(np.diff(flows[i+1:i+1+MIN_PERSISTENCE])) < GAP_TOL)\n", " ):\n", " idx = i\n", " shift = gap[i+1]\n", " break\n", "\n", " if idx is None:\n", " return g\n", "\n", " candidate = obs[idx:] - shift\n", "\n", " if ((candidate < 0) & (obs[idx:] >= 0)).any():\n", " return g\n", "\n", " if abs(shift) > 2 * np.nanmax(np.abs(obs)):\n", " return g\n", "\n", " corrected[idx:] = candidate\n", "\n", " g.loc[g.index[idx]:, \"repair_flag\"] = True\n", "\n", " expected_corr = np.empty_like(obs)\n", " expected_corr[0] = np.nan\n", "\n", " for t in range(1, len(obs)):\n", " expected_corr[t] = corrected[t-1] + flows[t-1]\n", "\n", " g[\"corrected_aum\"] = corrected\n", "\n", " return g\n", "\n", "# ============================================================\n", "# APPLY REPAIR\n", "# ============================================================\n", "\n", "df_repaired = (\n", " df.groupby(\n", " [\"Registrar Account - ID\",\"Product - Isin\"],\n", " group_keys=False\n", " )\n", " .apply(repair_group)\n", ")\n", "\n", "# ============================================================\n", "# REBUILD STOCK FILE\n", "# ============================================================\n", "\n", "stocks_repaired = stocks.copy()\n", "\n", "stocks_repaired[\"Quantity - AUM\"] = df_repaired[\"corrected_aum\"].fillna(\n", " stocks[\"Quantity - AUM\"]\n", ")\n", "\n", "# ============================================================\n", "# SAVE WITH ORIGINAL FORMAT\n", "# ============================================================\n", "\n", "stocks_repaired.to_csv(\n", " \"stock_repaired.csv\",\n", " sep=\";\",\n", " index=False\n", ")\n", "\n", "print(\"stock_repaired.csv successfully created\")" ] }, { "cell_type": "code", "execution_count": 15, "id": "f94f07b4-e053-4828-bbb1-3697f9a11751", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_5465/3656779442.py:4: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n", " .apply(repair_group)\n" ] }, { "ename": "KeyError", "evalue": "'expected_stock_corr'", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/indexes/base.py:3812\u001b[39m, in \u001b[36mIndex.get_loc\u001b[39m\u001b[34m(self, key)\u001b[39m\n\u001b[32m 3811\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m3812\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_engine\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcasted_key\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 3813\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/index.pyx:167\u001b[39m, in \u001b[36mpandas._libs.index.IndexEngine.get_loc\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/index.pyx:196\u001b[39m, in \u001b[36mpandas._libs.index.IndexEngine.get_loc\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/hashtable_class_helper.pxi:7088\u001b[39m, in \u001b[36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/hashtable_class_helper.pxi:7096\u001b[39m, in \u001b[36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[31mKeyError\u001b[39m: 'expected_stock_corr'", "\nThe above exception was the direct cause of the following exception:\n", "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 25\u001b[39m\n\u001b[32m 22\u001b[39m df[\u001b[33m\"\u001b[39m\u001b[33mexpected_stock\u001b[39m\u001b[33m\"\u001b[39m] = df[\u001b[33m\"\u001b[39m\u001b[33mprev_aum\u001b[39m\u001b[33m\"\u001b[39m] + df[\u001b[33m\"\u001b[39m\u001b[33mprev_flow\u001b[39m\u001b[33m\"\u001b[39m]\n\u001b[32m 23\u001b[39m df[\u001b[33m\"\u001b[39m\u001b[33mgap_before\u001b[39m\u001b[33m\"\u001b[39m] = df[\u001b[33m\"\u001b[39m\u001b[33mQuantity - AUM\u001b[39m\u001b[33m\"\u001b[39m] - df[\u001b[33m\"\u001b[39m\u001b[33mexpected_stock\u001b[39m\u001b[33m\"\u001b[39m]\n\u001b[32m---> \u001b[39m\u001b[32m25\u001b[39m df[\u001b[33m\"\u001b[39m\u001b[33mgap_after\u001b[39m\u001b[33m\"\u001b[39m] = df[\u001b[33m\"\u001b[39m\u001b[33mcorrected_aum\u001b[39m\u001b[33m\"\u001b[39m] - \u001b[43mdf\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mexpected_stock_corr\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[32m 27\u001b[39m df[\u001b[33m\"\u001b[39m\u001b[33mrupture_before\u001b[39m\u001b[33m\"\u001b[39m] = df[\u001b[33m\"\u001b[39m\u001b[33mgap_before\u001b[39m\u001b[33m\"\u001b[39m].abs() > GAP_TOL\n\u001b[32m 29\u001b[39m df[\u001b[33m\"\u001b[39m\u001b[33mrupture_after\u001b[39m\u001b[33m\"\u001b[39m] = df[\u001b[33m\"\u001b[39m\u001b[33mgap_after\u001b[39m\u001b[33m\"\u001b[39m].abs() > GAP_TOL\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/frame.py:4113\u001b[39m, in \u001b[36mDataFrame.__getitem__\u001b[39m\u001b[34m(self, key)\u001b[39m\n\u001b[32m 4111\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.columns.nlevels > \u001b[32m1\u001b[39m:\n\u001b[32m 4112\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._getitem_multilevel(key)\n\u001b[32m-> \u001b[39m\u001b[32m4113\u001b[39m indexer = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mcolumns\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 4114\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m is_integer(indexer):\n\u001b[32m 4115\u001b[39m indexer = [indexer]\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/indexes/base.py:3819\u001b[39m, in \u001b[36mIndex.get_loc\u001b[39m\u001b[34m(self, key)\u001b[39m\n\u001b[32m 3814\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(casted_key, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m (\n\u001b[32m 3815\u001b[39m \u001b[38;5;28misinstance\u001b[39m(casted_key, abc.Iterable)\n\u001b[32m 3816\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28many\u001b[39m(\u001b[38;5;28misinstance\u001b[39m(x, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;28;01mfor\u001b[39;00m x \u001b[38;5;129;01min\u001b[39;00m casted_key)\n\u001b[32m 3817\u001b[39m ):\n\u001b[32m 3818\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m InvalidIndexError(key)\n\u001b[32m-> \u001b[39m\u001b[32m3819\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01merr\u001b[39;00m\n\u001b[32m 3820\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[32m 3821\u001b[39m \u001b[38;5;66;03m# If we have a listlike key, _check_indexing_error will raise\u001b[39;00m\n\u001b[32m 3822\u001b[39m \u001b[38;5;66;03m# InvalidIndexError. Otherwise we fall through and re-raise\u001b[39;00m\n\u001b[32m 3823\u001b[39m \u001b[38;5;66;03m# the TypeError.\u001b[39;00m\n\u001b[32m 3824\u001b[39m \u001b[38;5;28mself\u001b[39m._check_indexing_error(key)\n", "\u001b[31mKeyError\u001b[39m: 'expected_stock_corr'" ] } ], "source": [ "df = (\n", " df\n", " .groupby([\"Registrar Account - ID\", \"Product - Isin\"], group_keys=False)\n", " .apply(repair_group)\n", ")\n", "\n", "# VALIDATION BEFORE / AFTER\n", "\n", "df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"prev_flow\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " .fillna(0)\n", ")\n", "\n", "df[\"expected_stock\"] = df[\"prev_aum\"] + df[\"prev_flow\"]\n", "df[\"gap_before\"] = df[\"Quantity - AUM\"] - df[\"expected_stock\"]\n", "\n", "df[\"gap_after\"] = df[\"corrected_aum\"] - df[\"expected_stock_corr\"]\n", "\n", "df[\"rupture_before\"] = df[\"gap_before\"].abs() > GAP_TOL\n", "\n", "df[\"rupture_after\"] = df[\"gap_after\"].abs() > GAP_TOL\n", "\n", "\n", "\n", "# SUMMARY\n", "\n", "\n", "summary = pd.DataFrame({\n", " \"Before repair\": [df[\"rupture_before\"].sum()],\n", " \"After repair\": [df[\"rupture_after\"].sum()],\n", " \"Repaired points\": [df[\"repair_flag\"].sum()]\n", "})\n", "\n", "print(summary)\n", "\n", "\n", "\n", "# BUILD REPAIRED DATASET\n", "\n", "\n", "stocks_repaired = stocks.copy()\n", "\n", "repair_map = df[[\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\",\n", " \"corrected_aum\",\n", " \"repair_flag\"\n", "]]\n", "\n", "stocks_repaired = stocks_repaired.merge(\n", " repair_map,\n", " on=keys,\n", " how=\"left\"\n", ")\n", "\n", "stocks_repaired[\"Quantity - AUM\"] = np.where(\n", " stocks_repaired[\"repair_flag\"] == True,\n", " stocks_repaired[\"corrected_aum\"],\n", " stocks_repaired[\"Quantity - AUM\"]\n", ")\n", "\n", "stocks_repaired.to_csv(\"stock_repaired.csv\", index=False)\n", "\n", "\n", "\n", "# COMPARISON RAW VS REPAIRED\n", "\n", "\n", "df_compare = stocks.merge(\n", " stocks_repaired,\n", " on=keys,\n", " how=\"inner\",\n", " suffixes=(\"_raw\",\"_repaired\")\n", ")\n", "\n", "df_compare[\"aum_diff\"] = (\n", " df_compare[\"Quantity - AUM_repaired\"]\n", " - df_compare[\"Quantity - AUM_raw\"]\n", ")\n", "\n", "print(\"\\nNUMBER OF MODIFIED OBSERVATIONS:\",\n", " (df_compare[\"aum_diff\"] != 0).sum())\n", "\n", "print(\"Share modified:\",\n", " round((df_compare[\"aum_diff\"] != 0).mean()*100,2), \"%\")\n", "\n", "print(\"\\nTOTAL AUM\")\n", "\n", "print(\"Raw total :\", df_compare[\"Quantity - AUM_raw\"].sum())\n", "print(\"Repaired total :\", df_compare[\"Quantity - AUM_repaired\"].sum())\n", "\n", "\n", "\n", "# RUPTURE DISTRIBUTION BEFORE / AFTER\n", "\n", "\n", "def rupture_distribution(df, flag):\n", "\n", " rupture_summary = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " .agg(\n", " n_ruptures=(flag,\"sum\"),\n", " total_obs=(flag,\"count\"),\n", " rupture_ratio=(flag,\"mean\")\n", " )\n", " .reset_index()\n", " )\n", "\n", " rs = rupture_summary.copy()\n", "\n", " bins = [0,0.01,0.10,0.30,1.01]\n", "\n", " labels = [\n", " \"Clean / quasi-clean (≤1%)\",\n", " \"Moderate (1–10%)\",\n", " \"High (10–30%)\",\n", " \"Severe (>30%)\"\n", " ]\n", "\n", " rs[\"rupture_class\"] = pd.cut(\n", " rs[\"rupture_ratio\"],\n", " bins=bins,\n", " labels=labels,\n", " include_lowest=True\n", " )\n", "\n", " dist = (\n", " rs[\"rupture_class\"]\n", " .value_counts(normalize=True)\n", " .sort_index()\n", " * 100\n", " ).round(1)\n", "\n", " return dist\n", "\n", "\n", "dist_before = rupture_distribution(df,\"rupture_before\")\n", "dist_after = rupture_distribution(df,\"rupture_after\")" ] }, { "cell_type": "code", "execution_count": null, "id": "54491736-58b3-4ef7-b6c4-5534ec796bce", "metadata": {}, "outputs": [], "source": [ "# DONUT CHART BEFORE / AFTER\n", "\n", "fig = go.Figure()\n", "\n", "fig.add_trace(go.Pie(\n", " labels=dist_before.index,\n", " values=dist_before.values,\n", " hole=0.45,\n", " name=\"Before repair\",\n", " domain=dict(x=[0,0.48]),\n", " textinfo=\"percent\"\n", "))\n", "\n", "fig.add_trace(go.Pie(\n", " labels=dist_after.index,\n", " values=dist_after.values,\n", " hole=0.45,\n", " name=\"After repair\",\n", " domain=dict(x=[0.52,1]),\n", " textinfo=\"percent\"\n", "))\n", "\n", "fig.update_layout(\n", " title=\"Rupture intensity distribution (Before vs After repair)\",\n", " annotations=[\n", " dict(text=\"Before repair\", x=0.22, y=0.5, showarrow=False),\n", " dict(text=\"After repair\", x=0.78, y=0.5, showarrow=False)\n", " ]\n", ")\n", "\n", "fig.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "d844f6f0-c0f4-4f71-8280-1fd39ced83b7", "metadata": {}, "outputs": [], "source": [ "# LOAD DATA\n", "\n", "aum = pd.read_csv(\"stock_repaired.csv\")\n", "\n", "aum[\"Centralisation Date\"] = pd.to_datetime(aum[\"Centralisation Date\"])\n", "\n", "\n", "# KEEP USEFUL COLUMNS\n", "\n", "aum = aum[[\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\",\n", " \"Quantity - AUM\",\n", " \"repair_flag\"\n", "]]\n", "\n", "flows = flows[[\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\",\n", " \"Quantity - NetFlows\"\n", "]]\n", "\n", "\n", "\n", "# AGGREGATE FLOWS\n", "\n", "flows = (\n", " flows\n", " .groupby(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " as_index=False\n", " )[\"Quantity - NetFlows\"]\n", " .sum()\n", ")\n", "\n", "\n", "\n", "# MERGE DATASETS\n", "\n", "df = aum.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", ")\n", "\n", "df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", "print(\"Dataset size:\", df.shape)\n", "\n", "\n", "\n", "# SORT DATA\n", "\n", "df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", ")\n", "\n", "\n", "\n", "# REBUILD ACCOUNTING IDENTITY\n", "\n", "df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"prev_flow\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " .fillna(0)\n", ")\n", "\n", "df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"prev_flow\"]\n", "\n", "\n", "\n", "# GAPS\n", "\n", "df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", "df[\"gap_abs\"] = df[\"gap\"].abs()\n", "\n", "df[\"gap_rel\"] = (\n", " df[\"gap_abs\"] /\n", " df[\"expected_aum\"].abs().clip(lower=1)\n", ")\n", "\n", "\n", "\n", "# ACCOUNTING CONSISTENCY\n", "\n", "print(\"\\nACCOUNTING GAP DISTRIBUTION\")\n", "\n", "print(df[\"gap_abs\"].describe())\n", "\n", "print(\"\\nRelative gap quantiles\")\n", "\n", "print(df[\"gap_rel\"].quantile([0.90,0.95,0.99]))\n", "\n", "\n", "\n", "# NEGATIVE AUM\n", "\n", "neg = (df[\"Quantity - AUM\"] < 0).sum()\n", "\n", "print(\"\\nNEGATIVE AUM:\", neg)\n", "\n", "\n", "\n", "# REPAIR RATE\n", "\n", "print(\"\\nREPAIR RATE\")\n", "\n", "print(df[\"repair_flag\"].mean())\n", "\n", "\n", "\n", "# AUM JUMPS\n", "\n", "\n", "df[\"prev_obs\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"aum_jump\"] = (\n", " df[\"Quantity - AUM\"] /\n", " df[\"prev_obs\"].replace(0,np.nan)\n", ")\n", "\n", "print(\"\\nAUM JUMP QUANTILES\")\n", "\n", "print(df[\"aum_jump\"].quantile([0.90,0.95,0.99]))\n", "\n", "\n", "\n", "# VISUAL CHECK\n", "\n", "\n", "def plot_series(account, isin):\n", "\n", " sub = df[\n", " (df[\"Registrar Account - ID\"] == account) &\n", " (df[\"Product - Isin\"] == isin)\n", " ]\n", "\n", " plt.figure(figsize=(8,3))\n", "\n", " plt.plot(\n", " sub[\"Centralisation Date\"],\n", " sub[\"Quantity - AUM\"],\n", " label=\"AUM\"\n", " )\n", "\n", " plt.plot(\n", " sub[\"Centralisation Date\"],\n", " sub[\"expected_aum\"],\n", " linestyle=\"--\",\n", " label=\"Expected AUM\"\n", " )\n", "\n", " plt.legend()\n", " plt.title(f\"Account {account} — ISIN {isin}\")\n", "\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "f7d759f7-64be-4d82-a79c-98cda407cfec", "metadata": { "scrolled": true }, "outputs": [], "source": [ "# COMPUTE AUM CHANGE\n", "\n", "df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"delta_aum\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", "df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", ")\n", "\n", "\n", "\n", "# FILTER VALID OBSERVATIONS\n", "\n", "\n", "diag = df[\n", " df[\"prev_aum\"].notna() &\n", " df[\"flow_lag\"].notna()\n", "]\n", "\n", "\n", "\n", "# SAMPLE FOR PLOTTING (dataset is large)\n", "\n", "\n", "sample = diag.sample(20000, random_state=1)\n", "\n", "\n", "\n", "# SCATTER PLOT\n", "\n", "\n", "plt.figure(figsize=(7,7))\n", "\n", "plt.scatter(\n", " sample[\"flow_lag\"],\n", " sample[\"delta_aum\"],\n", " alpha=0.3,\n", " s=5\n", ")\n", "\n", "# perfect accounting identity\n", "x = np.linspace(\n", " sample[\"flow_lag\"].min(),\n", " sample[\"flow_lag\"].max(),\n", " 100\n", ")\n", "\n", "plt.plot(x, x, color=\"red\", label=\"Perfect identity\")\n", "\n", "plt.xlabel(\"Flow (t-1)\")\n", "plt.ylabel(\"Δ AUM\")\n", "\n", "plt.title(\"AUM / Flow Accounting Diagnostic\")\n", "\n", "plt.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "d0a959c9-cfff-44cb-a1df-6c7275ec5b43", "metadata": {}, "outputs": [], "source": [ "df[\"implied_return\"] = (\n", " df[\"Quantity - AUM\"] - df[\"prev_aum\"] - df[\"flow_lag\"]\n", ") / df[\"prev_aum\"].replace(0, np.nan)\n", "\n", "print(df[\"implied_return\"].quantile([0.5,0.9,0.95,0.99]))" ] }, { "cell_type": "code", "execution_count": null, "id": "15111e8a-7d87-4c37-8122-daafe90a1ad5", "metadata": { "scrolled": true }, "outputs": [], "source": [ "# ============================================================\n", "# ADDITIONAL DATASET VALIDATION CHECKS (ROBUST VERSION)\n", "# ============================================================\n", "\n", "import numpy as np\n", "import pandas as pd\n", "\n", "print(\"\\n==============================\")\n", "print(\"ADDITIONAL DATA QUALITY CHECKS\")\n", "print(\"==============================\")\n", "\n", "# ------------------------------------------------------------\n", "# RECOMPUTE KEY VARIABLES IF NEEDED\n", "# ------------------------------------------------------------\n", "\n", "df = df.sort_values([\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"])\n", "\n", "df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"flow_lag\"] = df[\"flow_lag\"].fillna(0)\n", "\n", "df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", "df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "df[\"gap_abs\"] = df[\"gap\"].abs()\n", "\n", "df[\"delta_aum\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", "df[\"implied_return\"] = (\n", " df[\"Quantity - AUM\"] - df[\"prev_aum\"] - df[\"flow_lag\"]\n", ") / df[\"prev_aum\"].replace(0,np.nan)\n", "\n", "df[\"aum_jump\"] = (\n", " df[\"Quantity - AUM\"] /\n", " df[\"prev_aum\"].replace(0,np.nan)\n", ")\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 1. CHECK SERIES WHERE GAP IS STILL LARGE\n", "# ------------------------------------------------------------\n", "\n", "remaining_gaps = df[df[\"gap_abs\"] > 10]\n", "\n", "series_remaining = (\n", " remaining_gaps\n", " .groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " .size()\n", ")\n", "\n", "print(\"\\nSERIES STILL WITH LARGE ACCOUNTING GAPS:\", len(series_remaining))\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 2. GAP DISTRIBUTION\n", "# ------------------------------------------------------------\n", "\n", "print(\"\\nACCOUNTING GAP DISTRIBUTION\")\n", "print(df[\"gap_abs\"].quantile([0.5,0.9,0.95,0.99]))\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 3. IMPLIED RETURNS DISTRIBUTION\n", "# ------------------------------------------------------------\n", "\n", "print(\"\\nIMPLIED RETURN DISTRIBUTION\")\n", "print(df[\"implied_return\"].quantile([0.5,0.9,0.95,0.99]))\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 4. EXTREME RETURNS\n", "# ------------------------------------------------------------\n", "\n", "extreme_returns = df[df[\"implied_return\"].abs() > 2]\n", "\n", "print(\"\\nOBSERVATIONS WITH EXTREME IMPLIED RETURNS (>200%):\",\n", " len(extreme_returns))\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 5. FLOW / AUM ACCOUNTING CORRELATION\n", "# ------------------------------------------------------------\n", "\n", "valid = df[\n", " df[\"prev_aum\"].notna() &\n", " df[\"flow_lag\"].notna()\n", "]\n", "\n", "corr = valid[\"delta_aum\"].corr(valid[\"flow_lag\"])\n", "\n", "print(\"\\nCORRELATION ΔAUM vs FLOW:\", corr)\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 6. LARGE AUM JUMPS\n", "# ------------------------------------------------------------\n", "\n", "large_jumps = df[df[\"aum_jump\"].abs() > 5]\n", "\n", "print(\"\\nLARGE AUM JUMPS (>5x):\", len(large_jumps))\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 7. SERIES WITH HIGH GAP RATE\n", "# ------------------------------------------------------------\n", "\n", "series_gap_rate = (\n", " (df[\"gap_abs\"] > 10)\n", " .groupby([df[\"Registrar Account - ID\"], df[\"Product - Isin\"]])\n", " .mean()\n", ")\n", "\n", "problem_series = series_gap_rate[series_gap_rate > 0.2]\n", "\n", "print(\"\\nSERIES WITH >20% ACCOUNTING GAPS:\", len(problem_series))\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 8. TOTAL AUM STABILITY\n", "# ------------------------------------------------------------\n", "\n", "if \"df_compare\" in globals():\n", "\n", " raw_total = df_compare[\"Quantity - AUM_raw\"].sum()\n", " repaired_total = df_compare[\"Quantity - AUM_repaired\"].sum()\n", "\n", " print(\"\\nTOTAL AUM RAW:\", raw_total)\n", " print(\"TOTAL AUM REPAIRED:\", repaired_total)\n", "\n", " print(\"RELATIVE CHANGE:\",\n", " (repaired_total - raw_total) / raw_total)\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 9. PROPORTION OF SERIES REPAIRED\n", "# ------------------------------------------------------------\n", "\n", "if \"repair_flag\" in df.columns:\n", "\n", " series_repaired = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"repair_flag\"]\n", " .max()\n", " )\n", "\n", " print(\"\\nSERIES WITH AT LEAST ONE REPAIR:\",\n", " series_repaired.mean())\n", "\n", "\n", "# ------------------------------------------------------------\n", "# 10. WORST SERIES (MANUAL CHECK)\n", "# ------------------------------------------------------------\n", "\n", "worst_series = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"gap_abs\"]\n", " .max()\n", " .sort_values(ascending=False)\n", " .head(10)\n", ")\n", "\n", "print(\"\\nWORST SERIES AFTER REPAIR\")\n", "print(worst_series)" ] }, { "cell_type": "code", "execution_count": null, "id": "f9a4fd91-bb8b-4172-a267-cbe7f2e4fae7", "metadata": {}, "outputs": [], "source": [ "print(\"RUPTURES BEFORE:\", summary[\"Before repair\"].iloc[0])\n", "print(\"RUPTURES AFTER :\", summary[\"After repair\"].iloc[0])\n", "print(\"REDUCTION RATE :\", 1 - summary[\"After repair\"].iloc[0] /\n", " summary[\"Before repair\"].iloc[0])" ] }, { "cell_type": "code", "execution_count": null, "id": "68596521-a10a-479a-a6cd-36b6be3c55b9", "metadata": {}, "outputs": [], "source": [ "# CHECK IF REPAIR CREATED NEW NEGATIVE AUM\n", "\n", "df_compare = stocks.merge(\n", " stocks_repaired,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"inner\",\n", " suffixes=(\"_raw\",\"_repaired\")\n", ")\n", "\n", "neg_raw = (\n", " df_compare[df_compare[\"Quantity - AUM_raw\"] < 0]\n", " .groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " .size()\n", ")\n", "\n", "neg_rep = (\n", " df_compare[df_compare[\"Quantity - AUM_repaired\"] < 0]\n", " .groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " .size()\n", ")\n", "\n", "print(\"Negative series BEFORE repair:\", len(neg_raw))\n", "print(\"Negative series AFTER repair:\", len(neg_rep))\n", "\n", "created_neg = set(neg_rep.index) - set(neg_raw.index)\n", "\n", "print(\"\\nNumber of series where repair created negatives:\", len(created_neg))\n", "\n", "if len(created_neg) > 0:\n", " print(\"\\nSeries concerned:\")\n", " print(created_neg)\n", "\n", "\n", "\n", "for acc, isin in created_neg:\n", "\n", " sub = df_compare[\n", " (df_compare[\"Registrar Account - ID\"] == acc) &\n", " (df_compare[\"Product - Isin\"] == isin)\n", " ].sort_values(\"Centralisation Date\").reset_index(drop=True)\n", "\n", " # indices where repaired AUM becomes negative\n", " neg_idx = sub.index[sub[\"Quantity - AUM_repaired\"] < 0]\n", "\n", " print(\"\\n======================================\")\n", " print(\"Account:\", acc, \"ISIN:\", isin)\n", "\n", " for i in neg_idx:\n", "\n", " start = max(0, i-3)\n", " end = min(len(sub), i+3)\n", "\n", " print(\"\\nContext around created negative:\")\n", " print(\n", " sub.loc[start:end, [\n", " \"Centralisation Date\",\n", " \"Quantity - AUM_raw\",\n", " \"Quantity - AUM_repaired\"\n", " ]]\n", " )" ] }, { "cell_type": "code", "execution_count": null, "id": "203797d1-c380-406d-ac6e-78c4e1228966", "metadata": {}, "outputs": [], "source": [ "# top N worst series\n", "N = 20\n", "\n", "worst_series = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])[\"gap_abs\"]\n", " .max()\n", " .sort_values(ascending=False)\n", " .head(N)\n", ")\n", "\n", "print(worst_series)" ] }, { "cell_type": "code", "execution_count": null, "id": "17c6be03-3b76-41e7-906f-d86472bda274", "metadata": {}, "outputs": [], "source": [ "def plot_account_series(account, isin):\n", "\n", " sub = df[\n", " (df[\"Registrar Account - ID\"] == account) &\n", " (df[\"Product - Isin\"] == isin)\n", " ].sort_values(\"Centralisation Date\")\n", "\n", " plt.figure(figsize=(10,4))\n", "\n", " plt.plot(\n", " sub[\"Centralisation Date\"],\n", " sub[\"Quantity - AUM\"],\n", " label=\"AUM\",\n", " linewidth=2\n", " )\n", "\n", " plt.plot(\n", " sub[\"Centralisation Date\"],\n", " sub[\"expected_aum\"],\n", " linestyle=\"--\",\n", " label=\"Expected AUM\"\n", " )\n", "\n", " # highlight large gaps\n", " ruptures = sub[sub[\"gap_abs\"] > 10]\n", "\n", " plt.scatter(\n", " ruptures[\"Centralisation Date\"],\n", " ruptures[\"Quantity - AUM\"],\n", " color=\"red\",\n", " label=\"Rupture\",\n", " s=40\n", " )\n", "\n", " plt.title(f\"Account {account} | ISIN {isin}\")\n", " plt.legend()\n", " plt.grid(alpha=0.3)\n", "\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "02a22e9a-a71c-4212-8f0a-f33c18e4b530", "metadata": {}, "outputs": [], "source": [ "for acc, isin in worst_series.index:\n", " plot_account_series(acc, isin)" ] }, { "cell_type": "code", "execution_count": null, "id": "c72e2dc2-c35d-4608-a3da-57a37acf64c7", "metadata": {}, "outputs": [], "source": [ "def run_data_challenge(stocks, flows):\n", "\n", " # conversion dates\n", " stocks[\"Centralisation Date\"] = pd.to_datetime(stocks[\"Centralisation Date\"])\n", " flows[\"Centralisation Date\"] = pd.to_datetime(flows[\"Centralisation Date\"])\n", "\n", " # merge datasets\n", " df = stocks.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " # sort\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " )\n", "\n", " # previous values\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " ).fillna(0)\n", "\n", " # expected AUM\n", " df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", " # gap\n", " df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", " # build score\n", " df[\"score\"] = np.exp(-np.abs(df[\"gap\"]) / (df[\"expected_aum\"].abs()+1))\n", "\n", " # score timeline\n", " score_timeline = (\n", " df.groupby(\"Centralisation Date\")[\"score\"]\n", " .sum()\n", " .reset_index()\n", " .rename(columns={\"Centralisation Date\":\"date\",\n", " \"score\":\"sum_scores\"})\n", " .sort_values(\"date\")\n", " )\n", "\n", " code_changes = pd.DataFrame() # placeholder\n", "\n", " return score_timeline, code_changes" ] }, { "cell_type": "code", "execution_count": null, "id": "67787ce5-39ae-4a3a-ba22-e9d38bcaf8d3", "metadata": {}, "outputs": [], "source": [ "# ============================================================\n", "# RUN DATA CHALLENGE ON RAW AND CLEAN DATASETS\n", "# ============================================================\n", "\n", "DATASETS = {\n", " \"raw\": \"stocks.csv\",\n", " \"clean\": \"stock_repaired.csv\"\n", "}\n", "\n", "results = {}\n", "\n", "for name, file in DATASETS.items():\n", "\n", " print(\"\\n====================================\")\n", " print(\"RUNNING DATA CHALLENGE ON:\", name)\n", " print(\"====================================\")\n", "\n", " # load datasets\n", " stocks = pd.read_csv(file, low_memory=False)\n", " flows = pd.read_csv(\"flows.csv\", low_memory=False)\n", "\n", " # run the full algorithm\n", " score_timeline, code_changes = run_data_challenge(stocks, flows)\n", "\n", " # store results\n", " results[name] = score_timeline.copy()\n", "\n", "\n", "# ============================================================\n", "# BUILD COMPARISON TABLE\n", "# ============================================================\n", "\n", "comparison = (\n", " results[\"raw\"][[\"date\", \"sum_scores\"]]\n", " .rename(columns={\"sum_scores\": \"raw_scores\"})\n", " .merge(\n", " results[\"clean\"][[\"date\", \"sum_scores\"]]\n", " .rename(columns={\"sum_scores\": \"clean_scores\"}),\n", " on=\"date\",\n", " how=\"outer\"\n", " )\n", " .sort_values(\"date\")\n", ")\n", "\n", "# improvement from cleaning\n", "comparison[\"improvement\"] = (\n", " comparison[\"clean_scores\"] - comparison[\"raw_scores\"]\n", ")\n", "\n", "# relative improvement\n", "comparison[\"relative_improvement\"] = (\n", " comparison[\"improvement\"] /\n", " comparison[\"raw_scores\"].replace(0, np.nan)\n", ")\n", "\n", "print(\"\\n==============================\")\n", "print(\"RAW VS CLEAN SCORE COMPARISON\")\n", "print(\"==============================\")\n", "\n", "print(comparison.head())\n", "print(comparison.tail())" ] }, { "cell_type": "code", "execution_count": null, "id": "5bdb367f-3764-400a-a20d-793f8d004d82", "metadata": {}, "outputs": [], "source": [ "# ============================================================\n", "# PARAMETERS\n", "# ============================================================\n", "\n", "TARGET_DATE = pd.Timestamp(\"2025-10-31\")\n", "AUM_THRESHOLD = 5_000_000\n", "EXCLUDED = [\"OFF DISTRIBUTION\", \"PRIVATE CLIENTS\"]\n", "\n", "ALPHA = 5 # penalty strength for accounting error\n", "\n", "stocks[\"Centralisation Date\"] = pd.to_datetime(stocks[\"Centralisation Date\"])\n", "flows[\"Centralisation Date\"] = pd.to_datetime(flows[\"Centralisation Date\"])\n", "\n", "stocks[\"Registrar Account - ID\"] = stocks[\"Registrar Account - ID\"].astype(str).str.strip()\n", "flows[\"Registrar Account - ID\"] = flows[\"Registrar Account - ID\"].astype(str).str.strip()\n", "\n", "stocks[\"Product - Isin\"] = stocks[\"Product - Isin\"].astype(str)\n", "flows[\"Product - Isin\"] = flows[\"Product - Isin\"].astype(str)\n", "\n", "# ============================================================\n", "# REMOVE EXCLUDED ACCOUNTS\n", "# ============================================================\n", "\n", "stocks = stocks[~stocks[\"Registrar Account - ID\"].str.upper().isin(EXCLUDED)]\n", "flows = flows[~flows[\"Registrar Account - ID\"].str.upper().isin(EXCLUDED)]" ] }, { "cell_type": "code", "execution_count": null, "id": "b10832d6-988a-42cd-a6b6-4dfb690315b7", "metadata": {}, "outputs": [], "source": [ "# ============================================================\n", "# SELECT UNIVERSE AT TARGET DATE\n", "# ============================================================\n", "\n", "latest = stocks[stocks[\"Centralisation Date\"] == TARGET_DATE]\n", "\n", "account_aum = (\n", " latest.groupby(\"Registrar Account - ID\")[\"Quantity - AUM\"]\n", " .sum()\n", " .sort_values(ascending=False)\n", ")\n", "\n", "# remove negative values if any\n", "account_aum = account_aum[account_aum > 0]\n", "\n", "# cumulative coverage\n", "cum_aum = account_aum.cumsum()\n", "total_aum = account_aum.sum()\n", "\n", "coverage = cum_aum / total_aum\n", "\n", "# select accounts covering 97% of AUM\n", "selected_accounts = account_aum[coverage <= 0.97]\n", "\n", "# ensure at least one more account\n", "selected_accounts = account_aum.iloc[:len(selected_accounts)+1]\n", "\n", "# weights at t0\n", "weights = selected_accounts / selected_accounts.sum()\n", "\n", "print(\"\\nUNIVERSE SELECTION\")\n", "print(\"------------------\")\n", "print(\"Number of selected accounts:\", len(selected_accounts))\n", "print(\"Coverage:\", selected_accounts.sum() / total_aum)\n", "\n", "print(\"\\nTop accounts:\")\n", "print(selected_accounts.head(10))\n", "\n", "# ============================================================\n", "# BUILD DATASET\n", "# ============================================================\n", "\n", "df = stocks.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", ")\n", "\n", "df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", "df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", ")\n", "\n", "# ============================================================\n", "# COMPUTE PREVIOUS VALUES\n", "# ============================================================\n", "\n", "df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", ").fillna(0)\n", "\n", "df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", "df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", "df[\"rel_error\"] = (\n", " df[\"gap\"].abs() /\n", " df[\"expected_aum\"].abs().clip(lower=1)\n", ")\n", "\n", "# ============================================================\n", "# ACCOUNT LEVEL ERROR\n", "# ============================================================\n", "\n", "account_error = (\n", " df.groupby([\"Centralisation Date\",\"Registrar Account - ID\"])\n", " [\"rel_error\"]\n", " .mean()\n", " .reset_index()\n", ")\n", "\n", "# ============================================================\n", "# FAST SCORE BACKWARD PROPAGATION\n", "# ============================================================\n", "\n", "# pivot errors into matrix\n", "error_matrix = (\n", " account_error\n", " .pivot(\n", " index=\"Centralisation Date\",\n", " columns=\"Registrar Account - ID\",\n", " values=\"rel_error\"\n", " )\n", ")\n", "\n", "# keep only selected accounts\n", "error_matrix = error_matrix[selected_accounts.index]\n", "\n", "# fill missing errors\n", "error_matrix = error_matrix.fillna(0)\n", "\n", "# keep only dates <= target\n", "error_matrix = error_matrix.loc[error_matrix.index <= TARGET_DATE]\n", "\n", "# sort dates\n", "error_matrix = error_matrix.sort_index()\n", "\n", "dates = error_matrix.index.values\n", "\n", "# convert to numpy for speed\n", "errors = error_matrix.values\n", "\n", "# initial scores\n", "scores = weights.loc[selected_accounts.index].values\n", "\n", "score_history = []\n", "\n", "# backward propagation\n", "for i in range(len(dates)-1, -1, -1):\n", "\n", " err = errors[i]\n", "\n", " quality = np.exp(-ALPHA * err)\n", "\n", " scores = scores * quality\n", "\n", " score_history.append({\n", " \"date\": dates[i],\n", " \"sum_scores\": scores.sum()\n", " })\n", "\n", "score_timeline = (\n", " pd.DataFrame(score_history)\n", " .sort_values(\"date\")\n", ")\n", "\n", "# ============================================================\n", "# NORMALISE\n", "# ============================================================\n", "\n", "initial_score = score_timeline[\"sum_scores\"].iloc[-1]\n", "\n", "score_timeline[\"score_retention\"] = (\n", " score_timeline[\"sum_scores\"] / initial_score\n", ")\n", "\n", "# ============================================================\n", "# RESULTS\n", "# ============================================================\n", "\n", "print(\"\\nSCORE TIMELINE\")\n", "print(score_timeline.head())\n", "print(score_timeline.tail())\n", "\n", "# ============================================================\n", "# PLOT\n", "# ============================================================\n", "\n", "plt.figure(figsize=(8,4))\n", "\n", "plt.plot(\n", " score_timeline[\"date\"],\n", " score_timeline[\"score_retention\"]\n", ")\n", "\n", "plt.title(\"Score retention when moving backward in time\")\n", "plt.xlabel(\"Date\")\n", "plt.ylabel(\"Σ Scores / Σ Scores (t0)\")\n", "plt.grid(alpha=0.3)\n", "\n", "plt.show()\n", "\n", "# ============================================================\n", "# SAVE\n", "# ============================================================\n", "\n", "score_timeline.to_csv(\n", " \"data_challenge_score_timeline.csv\",\n", " index=False\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "86d82213-d155-4feb-817e-ef1df50578e8", "metadata": {}, "outputs": [], "source": [ "# ============================================================\n", "# PRECOMPUTE PORTFOLIOS BY DATE\n", "# ============================================================\n", "\n", "portfolio_by_date = {\n", " d: g.droplevel(0)\n", " for d, g in portfolio.groupby(level=0)\n", "}\n", "\n", "flows_by_date = {\n", " d: g.droplevel(0)\n", " for d, g in flows_matrix.groupby(level=0)\n", "}\n", "\n", "# ============================================================\n", "# CODE SURGERY SEARCH (FAST + ROBUST)\n", "# ============================================================\n", "\n", "code_changes = []\n", "\n", "for row in ruptures.itertuples():\n", "\n", " date = row._1\n", " acc = row._2\n", "\n", " # find previous date\n", " prev_date = score_timeline.loc[\n", " score_timeline[\"date\"] < date,\n", " \"date\"\n", " ].max()\n", "\n", " if pd.isna(prev_date):\n", " continue\n", "\n", " if date not in portfolio_by_date:\n", " continue\n", "\n", " if prev_date not in portfolio_by_date:\n", " continue\n", "\n", " # portfolio at t\n", " port_today = portfolio_by_date[date]\n", "\n", " if acc not in port_today.index:\n", " continue\n", "\n", " port_t = port_today.loc[acc]\n", "\n", " # portfolio at t-1\n", " prev_port = portfolio_by_date[prev_date]\n", "\n", " # flows at t-1\n", " prev_flow = flows_by_date.get(prev_date)\n", "\n", " # align flows to portfolio\n", " if prev_flow is not None:\n", "\n", " prev_flow = (\n", " prev_flow\n", " .reindex(index=prev_port.index, columns=prev_port.columns)\n", " .fillna(0)\n", " )\n", "\n", " else:\n", "\n", " prev_flow = pd.DataFrame(\n", " 0,\n", " index=prev_port.index,\n", " columns=prev_port.columns\n", " )\n", "\n", " # convert to numpy\n", " prev_port_mat = prev_port.values\n", " prev_flow_mat = prev_flow.values\n", "\n", " # predicted portfolio\n", " predicted = prev_port_mat + prev_flow_mat\n", "\n", " port_t_vec = port_t.reindex(prev_port.columns).fillna(0).values\n", "\n", " # compute error vectorised\n", " diff = np.abs(predicted - port_t_vec)\n", "\n", " errors = diff.sum(axis=1) / (np.abs(port_t_vec).sum() + 1)\n", "\n", " if len(errors) == 0:\n", " continue\n", "\n", " best_idx = errors.argmin()\n", "\n", " if best_idx >= len(prev_port.index):\n", " continue\n", "\n", " best_code = prev_port.index[best_idx]\n", " best_error = errors[best_idx]\n", "\n", " if best_code != acc and best_error < 0.3:\n", "\n", " code_changes.append({\n", " \"date\": date,\n", " \"old_code\": acc,\n", " \"new_code\": best_code,\n", " \"portfolio_error\": best_error\n", " })\n", "\n", "\n", "# ============================================================\n", "# RESULTS\n", "# ============================================================\n", "\n", "code_changes = pd.DataFrame(code_changes)\n", "\n", "print(\"\\nDetected distributor code changes:\")\n", "print(code_changes.head())\n", "\n", "code_changes.to_csv(\"detected_code_changes.csv\", index=False)" ] }, { "cell_type": "code", "execution_count": null, "id": "452b8321-26c5-4229-9992-43c38eb5253f", "metadata": {}, "outputs": [], "source": [ "def detect_code_changes(portfolio, flows_matrix, ruptures, score_timeline):\n", "\n", " # ============================================================\n", " # PRECOMPUTE PORTFOLIOS BY DATE\n", " # ============================================================\n", "\n", " portfolio_by_date = {\n", " d: g.droplevel(0)\n", " for d, g in portfolio.groupby(level=0)\n", " }\n", "\n", " flows_by_date = {\n", " d: g.droplevel(0)\n", " for d, g in flows_matrix.groupby(level=0)\n", " }\n", "\n", " # ============================================================\n", " # CODE SURGERY SEARCH\n", " # ============================================================\n", "\n", " code_changes = []\n", "\n", " for row in ruptures.itertuples():\n", "\n", " date = row._1\n", " acc = row._2\n", "\n", " # find previous date\n", " prev_date = score_timeline.loc[\n", " score_timeline[\"date\"] < date,\n", " \"date\"\n", " ].max()\n", "\n", " if pd.isna(prev_date):\n", " continue\n", "\n", " if date not in portfolio_by_date:\n", " continue\n", "\n", " if prev_date not in portfolio_by_date:\n", " continue\n", "\n", " # portfolio at t\n", " port_today = portfolio_by_date[date]\n", "\n", " if acc not in port_today.index:\n", " continue\n", "\n", " port_t = port_today.loc[acc]\n", "\n", " # portfolio at t-1\n", " prev_port = portfolio_by_date[prev_date]\n", "\n", " # flows at t-1\n", " prev_flow = flows_by_date.get(prev_date)\n", "\n", " # align flows to portfolio\n", " if prev_flow is not None:\n", "\n", " prev_flow = (\n", " prev_flow\n", " .reindex(index=prev_port.index, columns=prev_port.columns)\n", " .fillna(0)\n", " )\n", "\n", " else:\n", "\n", " prev_flow = pd.DataFrame(\n", " 0,\n", " index=prev_port.index,\n", " columns=prev_port.columns\n", " )\n", "\n", " # convert to numpy\n", " prev_port_mat = prev_port.values\n", " prev_flow_mat = prev_flow.values\n", "\n", " # predicted portfolio\n", " predicted = prev_port_mat + prev_flow_mat\n", "\n", " port_t_vec = port_t.reindex(prev_port.columns).fillna(0).values\n", "\n", " # compute error vectorised\n", " diff = np.abs(predicted - port_t_vec)\n", "\n", " errors = diff.sum(axis=1) / (np.abs(port_t_vec).sum() + 1)\n", "\n", " if len(errors) == 0:\n", " continue\n", "\n", " best_idx = errors.argmin()\n", "\n", " if best_idx >= len(prev_port.index):\n", " continue\n", "\n", " best_code = prev_port.index[best_idx]\n", " best_error = errors[best_idx]\n", "\n", " if best_code != acc and best_error < 0.3:\n", "\n", " code_changes.append({\n", " \"date\": date,\n", " \"old_code\": acc,\n", " \"new_code\": best_code,\n", " \"portfolio_error\": best_error\n", " })\n", "\n", " # ============================================================\n", " # RESULTS\n", " # ============================================================\n", "\n", " code_changes = pd.DataFrame(code_changes)\n", "\n", " print(\"\\nDetected distributor code changes:\")\n", " print(code_changes.head())\n", "\n", " # nouveau nom de fichier\n", " code_changes.to_csv(\"detected_code_changes_filtered.csv\", index=False)\n", "\n", " return code_changes\n", "\n", "code_changes = detect_code_changes(\n", " portfolio,\n", " flows_matrix,\n", " ruptures,\n", " score_timeline\n", ")" ] }, { "cell_type": "code", "execution_count": 4, "id": "068da1e3-9de7-49d1-bda4-663b02f6d76a", "metadata": {}, "outputs": [], "source": [ "def detect_code_changes_fast(portfolio, flows_matrix, ruptures, score_timeline):\n", "\n", " # ============================================================\n", " # PRECOMPUTE PORTFOLIOS BY DATE\n", " # ============================================================\n", "\n", " portfolio_by_date = {\n", " d: g.droplevel(0)\n", " for d, g in portfolio.groupby(level=0)\n", " }\n", "\n", " flows_by_date = {\n", " d: g.droplevel(0)\n", " for d, g in flows_matrix.groupby(level=0)\n", " }\n", "\n", " # ============================================================\n", " # PRECOMPUTE PREVIOUS DATES\n", " # ============================================================\n", "\n", " dates = sorted(score_timeline[\"date\"].unique())\n", "\n", " prev_date_map = {\n", " dates[i]: dates[i - 1] if i > 0 else None\n", " for i in range(len(dates))\n", " }\n", "\n", " # ============================================================\n", " # CODE SURGERY SEARCH\n", " # ============================================================\n", "\n", " code_changes = []\n", "\n", " from tqdm import tqdm\n", " \n", " for row in tqdm(ruptures.itertuples(), total=len(ruptures)):\n", " date = row._1\n", " acc = row._2\n", "\n", " prev_date = prev_date_map.get(date)\n", "\n", " if prev_date is None:\n", " continue\n", "\n", " if date not in portfolio_by_date:\n", " continue\n", "\n", " if prev_date not in portfolio_by_date:\n", " continue\n", "\n", " port_today = portfolio_by_date[date]\n", "\n", " if acc not in port_today.index:\n", " continue\n", "\n", " port_t = port_today.loc[acc]\n", "\n", " prev_port = portfolio_by_date[prev_date]\n", "\n", " # ========================================================\n", " # LIMIT CANDIDATES (shared ISINs)\n", " # ========================================================\n", "\n", " held_isins = port_t[port_t > 0].index\n", "\n", " if len(held_isins) == 0:\n", " continue\n", "\n", " candidate_mask = prev_port[held_isins].sum(axis=1) > 0\n", "\n", " candidates = prev_port.index[candidate_mask]\n", "\n", " if len(candidates) == 0:\n", " continue\n", "\n", " prev_port = prev_port.loc[candidates]\n", "\n", " prev_flow = flows_by_date.get(prev_date)\n", "\n", " if prev_flow is not None:\n", "\n", " prev_flow = prev_flow.reindex(\n", " index=candidates,\n", " columns=prev_port.columns\n", " ).fillna(0)\n", "\n", " else:\n", "\n", " prev_flow = pd.DataFrame(\n", " 0,\n", " index=candidates,\n", " columns=prev_port.columns\n", " )\n", "\n", " # ========================================================\n", " # VECTORISED ERROR COMPUTATION\n", " # ========================================================\n", "\n", " prev_port_mat = prev_port.values\n", " prev_flow_mat = prev_flow.values\n", "\n", " predicted = prev_port_mat + prev_flow_mat\n", "\n", " port_t_vec = port_t.reindex(prev_port.columns).fillna(0).values\n", "\n", " diff = np.abs(predicted - port_t_vec)\n", "\n", " errors = diff.sum(axis=1) / (np.abs(port_t_vec).sum() + 1)\n", "\n", " if len(errors) == 0:\n", " continue\n", "\n", " best_idx = errors.argmin()\n", "\n", " best_code = prev_port.index[best_idx]\n", " best_error = errors[best_idx]\n", "\n", " if best_code != acc and best_error < 0.3:\n", "\n", " code_changes.append({\n", " \"date\": date,\n", " \"old_code\": acc,\n", " \"new_code\": best_code,\n", " \"portfolio_error\": best_error\n", " })\n", "\n", " # ============================================================\n", " # RESULTS\n", " # ============================================================\n", "\n", " code_changes = pd.DataFrame(code_changes)\n", "\n", " print(\"\\nDetected distributor code changes:\")\n", " print(code_changes.head())\n", "\n", " code_changes.to_csv(\"detected_code_changes_fast.csv\", index=False)\n", "\n", " return code_changes" ] }, { "cell_type": "code", "execution_count": 3, "id": "2b332049-db18-470a-9249-248f01e8ca36", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ " 11%|█ | 2068/18582 [00:24<03:14, 85.09it/s] \n" ] }, { "ename": "KeyboardInterrupt", "evalue": "", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 145\u001b[39m\n\u001b[32m 138\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m code_changes\n\u001b[32m 141\u001b[39m \u001b[38;5;66;03m# ============================================================\u001b[39;00m\n\u001b[32m 142\u001b[39m \u001b[38;5;66;03m# RUN\u001b[39;00m\n\u001b[32m 143\u001b[39m \u001b[38;5;66;03m# ============================================================\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m145\u001b[39m code_changes = \u001b[43mdetect_code_changes_fast\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 146\u001b[39m \u001b[43m \u001b[49m\u001b[43mportfolio\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 147\u001b[39m \u001b[43m \u001b[49m\u001b[43mflows_matrix\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 148\u001b[39m \u001b[43m \u001b[49m\u001b[43mruptures\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 149\u001b[39m \u001b[43m \u001b[49m\u001b[43mscore_timeline\u001b[49m\n\u001b[32m 150\u001b[39m \u001b[43m)\u001b[49m\n", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 69\u001b[39m, in \u001b[36mdetect_code_changes_fast\u001b[39m\u001b[34m(portfolio, flows_matrix, ruptures, score_timeline)\u001b[39m\n\u001b[32m 66\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(held_isins) == \u001b[32m0\u001b[39m:\n\u001b[32m 67\u001b[39m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m69\u001b[39m candidate_mask = \u001b[43mprev_port\u001b[49m\u001b[43m[\u001b[49m\u001b[43mheld_isins\u001b[49m\u001b[43m]\u001b[49m\u001b[43m.\u001b[49m\u001b[43msum\u001b[49m\u001b[43m(\u001b[49m\u001b[43maxis\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m1\u001b[39;49m\u001b[43m)\u001b[49m > \u001b[32m0\u001b[39m\n\u001b[32m 71\u001b[39m candidates = prev_port.index[candidate_mask]\n\u001b[32m 73\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(candidates) == \u001b[32m0\u001b[39m:\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/frame.py:11697\u001b[39m, in \u001b[36mDataFrame.sum\u001b[39m\u001b[34m(self, axis, skipna, numeric_only, min_count, **kwargs)\u001b[39m\n\u001b[32m 11688\u001b[39m \u001b[38;5;129m@doc\u001b[39m(make_doc(\u001b[33m\"\u001b[39m\u001b[33msum\u001b[39m\u001b[33m\"\u001b[39m, ndim=\u001b[32m2\u001b[39m))\n\u001b[32m 11689\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34msum\u001b[39m(\n\u001b[32m 11690\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 11695\u001b[39m **kwargs,\n\u001b[32m 11696\u001b[39m ):\n\u001b[32m> \u001b[39m\u001b[32m11697\u001b[39m result = \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43msum\u001b[49m\u001b[43m(\u001b[49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mskipna\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnumeric_only\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmin_count\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 11698\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m result.__finalize__(\u001b[38;5;28mself\u001b[39m, method=\u001b[33m\"\u001b[39m\u001b[33msum\u001b[39m\u001b[33m\"\u001b[39m)\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/generic.py:12571\u001b[39m, in \u001b[36mNDFrame.sum\u001b[39m\u001b[34m(self, axis, skipna, numeric_only, min_count, **kwargs)\u001b[39m\n\u001b[32m 12563\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34msum\u001b[39m(\n\u001b[32m 12564\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 12565\u001b[39m axis: Axis | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[32m0\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 12569\u001b[39m **kwargs,\n\u001b[32m 12570\u001b[39m ):\n\u001b[32m> \u001b[39m\u001b[32m12571\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_min_count_stat_function\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 12572\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msum\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnanops\u001b[49m\u001b[43m.\u001b[49m\u001b[43mnansum\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mskipna\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnumeric_only\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmin_count\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\n\u001b[32m 12573\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/generic.py:12554\u001b[39m, in \u001b[36mNDFrame._min_count_stat_function\u001b[39m\u001b[34m(self, name, func, axis, skipna, numeric_only, min_count, **kwargs)\u001b[39m\n\u001b[32m 12551\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m axis \u001b[38;5;129;01mis\u001b[39;00m lib.no_default:\n\u001b[32m 12552\u001b[39m axis = \u001b[32m0\u001b[39m\n\u001b[32m> \u001b[39m\u001b[32m12554\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_reduce\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 12555\u001b[39m \u001b[43m \u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12556\u001b[39m \u001b[43m \u001b[49m\u001b[43mname\u001b[49m\u001b[43m=\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12557\u001b[39m \u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[43m=\u001b[49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12558\u001b[39m \u001b[43m \u001b[49m\u001b[43mskipna\u001b[49m\u001b[43m=\u001b[49m\u001b[43mskipna\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12559\u001b[39m \u001b[43m \u001b[49m\u001b[43mnumeric_only\u001b[49m\u001b[43m=\u001b[49m\u001b[43mnumeric_only\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12560\u001b[39m \u001b[43m \u001b[49m\u001b[43mmin_count\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmin_count\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12561\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/frame.py:11593\u001b[39m, in \u001b[36mDataFrame._reduce\u001b[39m\u001b[34m(self, op, name, axis, skipna, numeric_only, filter_type, **kwds)\u001b[39m\n\u001b[32m 11591\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m out_dtype \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m out.dtype != \u001b[33m\"\u001b[39m\u001b[33mboolean\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m 11592\u001b[39m out = out.astype(out_dtype)\n\u001b[32m> \u001b[39m\u001b[32m11593\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m (\u001b[43mdf\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_mgr\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_dtypes\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m == \u001b[38;5;28mobject\u001b[39m).any() \u001b[38;5;129;01mand\u001b[39;00m name \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m [\u001b[33m\"\u001b[39m\u001b[33many\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mall\u001b[39m\u001b[33m\"\u001b[39m]:\n\u001b[32m 11594\u001b[39m out = out.astype(\u001b[38;5;28mobject\u001b[39m)\n\u001b[32m 11595\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m) == \u001b[32m0\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m out.dtype == \u001b[38;5;28mobject\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m name \u001b[38;5;129;01min\u001b[39;00m (\u001b[33m\"\u001b[39m\u001b[33msum\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mprod\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m 11596\u001b[39m \u001b[38;5;66;03m# Even if we are object dtype, follow numpy and return\u001b[39;00m\n\u001b[32m 11597\u001b[39m \u001b[38;5;66;03m# float64, see test_apply_funcs_over_empty\u001b[39;00m\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/internals/managers.py:289\u001b[39m, in \u001b[36mBaseBlockManager.get_dtypes\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 287\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mget_dtypes\u001b[39m(\u001b[38;5;28mself\u001b[39m) -> npt.NDArray[np.object_]:\n\u001b[32m 288\u001b[39m dtypes = np.array([blk.dtype \u001b[38;5;28;01mfor\u001b[39;00m blk \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m.blocks], dtype=\u001b[38;5;28mobject\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m289\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m dtypes.take(\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mblknos\u001b[49m)\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/internals/managers.py:192\u001b[39m, in \u001b[36mBaseBlockManager.blknos\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 182\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 183\u001b[39m \u001b[33;03mSuppose we want to find the array corresponding to our i'th column.\u001b[39;00m\n\u001b[32m 184\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 188\u001b[39m \u001b[33;03mself.blocks[self.blknos[i]]\u001b[39;00m\n\u001b[32m 189\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 190\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._blknos \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 191\u001b[39m \u001b[38;5;66;03m# Note: these can be altered by other BlockManager methods.\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m192\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_rebuild_blknos_and_blklocs\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 194\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._blknos\n", "\u001b[31mKeyboardInterrupt\u001b[39m: " ] } ], "source": [ "# ============================================================\n", "# RUN\n", "# ============================================================\n", "\n", "code_changes = detect_code_changes_fast(\n", " portfolio,\n", " flows_matrix,\n", " ruptures,\n", " score_timeline\n", ")" ] }, { "cell_type": "code", "execution_count": 5, "id": "3054206e-13b8-4931-a1d8-e3cbb97eab7a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "====================================\n", "RUNNING DATA CHALLENGE ON: raw\n", "====================================\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 18582/18582 [02:27<00:00, 126.15it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Detected distributor code changes:\n", " date old_code new_code portfolio_error\n", "0 2015-02-28 200001285 200001992 0.090028\n", "1 2015-02-28 200001771 420304 0.049979\n", "2 2015-02-28 200001894 366541 0.215146\n", "3 2015-02-28 200002064 412736 0.210827\n", "4 2015-02-28 200002109 406337 0.173276\n", "\n", "====================================\n", "RUNNING DATA CHALLENGE ON: clean\n", "====================================\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 18521/18521 [02:34<00:00, 119.82it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Detected distributor code changes:\n", " date old_code new_code portfolio_error\n", "0 2015-02-28 200001285 200001992 0.090028\n", "1 2015-02-28 200001771 420304 0.049979\n", "2 2015-02-28 200001894 366541 0.215146\n", "3 2015-02-28 200002064 412736 0.210827\n", "4 2015-02-28 200002109 406337 0.173276\n", "\n", "==============================\n", "RAW VS CLEAN SCORE COMPARISON\n", "==============================\n", " date raw_scores clean_scores improvement relative_improvement\n", "0 2015-01-31 5639.521248 5639.524696 0.003448 6.113170e-07\n", "1 2015-02-28 5639.521248 5639.524696 0.003448 6.113170e-07\n", "2 2015-03-31 5656.707028 5656.722266 0.015238 2.693820e-06\n", "3 2015-04-30 5701.063923 5701.084181 0.020258 3.553334e-06\n", "4 2015-05-31 5728.963044 5728.994764 0.031720 5.536759e-06\n", " date raw_scores clean_scores improvement relative_improvement\n", "125 2025-06-30 11703.280129 11703.402856 0.122727 0.000010\n", "126 2025-07-31 11855.613232 11855.796565 0.183333 0.000015\n", "127 2025-08-31 11962.226141 11962.553334 0.327193 0.000027\n", "128 2025-09-30 12046.634770 12046.966956 0.332185 0.000028\n", "129 2025-10-31 12189.843400 12190.351082 0.507683 0.000042\n" ] } ], "source": [ "# ============================================================\n", "# RUN DATA CHALLENGE ON RAW AND CLEAN DATASETS\n", "# ============================================================\n", "\n", "DATASETS = {\n", " \"raw\": \"stocks.csv\",\n", " \"clean\": \"stock_repaired.csv\"\n", "}\n", "\n", "results = {}\n", "code_changes_results = {}\n", "\n", "for name, file in DATASETS.items():\n", "\n", " print(\"\\n====================================\")\n", " print(\"RUNNING DATA CHALLENGE ON:\", name)\n", " print(\"====================================\")\n", "\n", " # ============================================================\n", " # LOAD DATA\n", " # ============================================================\n", "\n", " stocks = pd.read_csv(file, low_memory=False)\n", " flows = pd.read_csv(\"flows.csv\", low_memory=False)\n", "\n", " stocks[\"Centralisation Date\"] = pd.to_datetime(stocks[\"Centralisation Date\"])\n", " flows[\"Centralisation Date\"] = pd.to_datetime(flows[\"Centralisation Date\"])\n", "\n", " stocks[\"Registrar Account - ID\"] = stocks[\"Registrar Account - ID\"].astype(str).str.strip()\n", " flows[\"Registrar Account - ID\"] = flows[\"Registrar Account - ID\"].astype(str).str.strip()\n", "\n", " stocks[\"Product - Isin\"] = stocks[\"Product - Isin\"].astype(str)\n", " flows[\"Product - Isin\"] = flows[\"Product - Isin\"].astype(str)\n", "\n", " # ============================================================\n", " # BUILD DATASET\n", " # ============================================================\n", "\n", " df = stocks.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " )\n", "\n", " # ============================================================\n", " # ACCOUNTING IDENTITY\n", " # ============================================================\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " ).fillna(0)\n", "\n", " df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", " df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", " df[\"rel_error\"] = (\n", " df[\"gap\"].abs() /\n", " df[\"expected_aum\"].abs().clip(lower=1)\n", " )\n", "\n", " # ============================================================\n", " # ACCOUNT LEVEL ERROR\n", " # ============================================================\n", "\n", " account_error = (\n", " df.groupby([\"Centralisation Date\",\"Registrar Account - ID\"])\n", " [\"rel_error\"]\n", " .mean()\n", " .reset_index()\n", " )\n", "\n", " RUPTURE_THRESHOLD = 0.5\n", "\n", " ruptures = account_error[\n", " account_error[\"rel_error\"] > RUPTURE_THRESHOLD\n", " ]\n", "\n", " # ============================================================\n", " # BUILD PORTFOLIO MATRICES\n", " # ============================================================\n", "\n", " portfolio = (\n", " stocks\n", " .pivot_table(\n", " index=[\"Centralisation Date\",\"Registrar Account - ID\"],\n", " columns=\"Product - Isin\",\n", " values=\"Quantity - AUM\",\n", " aggfunc=\"sum\"\n", " )\n", " .fillna(0)\n", " )\n", "\n", " flows_matrix = (\n", " flows\n", " .pivot_table(\n", " index=[\"Centralisation Date\",\"Registrar Account - ID\"],\n", " columns=\"Product - Isin\",\n", " values=\"Quantity - NetFlows\",\n", " aggfunc=\"sum\"\n", " )\n", " .fillna(0)\n", " )\n", "\n", " # ============================================================\n", " # SCORE COMPUTATION\n", " # ============================================================\n", "\n", " error_matrix = (\n", " account_error\n", " .pivot(\n", " index=\"Centralisation Date\",\n", " columns=\"Registrar Account - ID\",\n", " values=\"rel_error\"\n", " )\n", " .fillna(0)\n", " )\n", "\n", " dates = error_matrix.index.values\n", " errors = error_matrix.values\n", "\n", " scores = np.ones(errors.shape[1])\n", "\n", " score_history = []\n", "\n", " for i in range(len(dates)-1, -1, -1):\n", "\n", " quality = np.exp(-5 * errors[i])\n", "\n", " scores = scores * quality\n", "\n", " score_history.append({\n", " \"date\": dates[i],\n", " \"sum_scores\": scores.sum()\n", " })\n", "\n", " score_timeline = (\n", " pd.DataFrame(score_history)\n", " .sort_values(\"date\")\n", " )\n", "\n", " initial_score = score_timeline[\"sum_scores\"].iloc[-1]\n", "\n", " score_timeline[\"score_retention\"] = (\n", " score_timeline[\"sum_scores\"] / initial_score\n", " )\n", "\n", " results[name] = score_timeline.copy()\n", "\n", " # ============================================================\n", " # CODE SURGERY\n", " # ============================================================\n", "\n", " code_changes = detect_code_changes_fast(\n", " portfolio,\n", " flows_matrix,\n", " ruptures,\n", " score_timeline\n", " )\n", "\n", " code_changes_results[name] = code_changes\n", "\n", "\n", "# ============================================================\n", "# BUILD COMPARISON TABLE\n", "# ============================================================\n", "\n", "comparison = (\n", " results[\"raw\"][[\"date\", \"sum_scores\"]]\n", " .rename(columns={\"sum_scores\": \"raw_scores\"})\n", " .merge(\n", " results[\"clean\"][[\"date\", \"sum_scores\"]]\n", " .rename(columns={\"sum_scores\": \"clean_scores\"}),\n", " on=\"date\",\n", " how=\"outer\"\n", " )\n", " .sort_values(\"date\")\n", ")\n", "\n", "comparison[\"improvement\"] = (\n", " comparison[\"clean_scores\"] - comparison[\"raw_scores\"]\n", ")\n", "\n", "comparison[\"relative_improvement\"] = (\n", " comparison[\"improvement\"] /\n", " comparison[\"raw_scores\"].replace(0, np.nan)\n", ")\n", "\n", "print(\"\\n==============================\")\n", "print(\"RAW VS CLEAN SCORE COMPARISON\")\n", "print(\"==============================\")\n", "\n", "print(comparison.head())\n", "print(comparison.tail())" ] }, { "cell_type": "code", "execution_count": 7, "id": "15c60063-b8e5-43f0-a662-67a2f5601408", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TOTAL RAW SCORE : 1102753.964620689\n", "TOTAL CLEAN SCORE : 1102778.1794964853\n", "ABSOLUTE IMPROVEMENT : 24.21487579634413\n", "RELATIVE IMPROVEMENT : 2.1958547938363796e-05\n" ] } ], "source": [ "total_raw = comparison[\"raw_scores\"].sum()\n", "total_clean = comparison[\"clean_scores\"].sum()\n", "\n", "absolute_gain = total_clean - total_raw\n", "relative_gain = absolute_gain / total_raw\n", "\n", "print(\"TOTAL RAW SCORE :\", total_raw)\n", "print(\"TOTAL CLEAN SCORE :\", total_clean)\n", "\n", "print(\"ABSOLUTE IMPROVEMENT :\", absolute_gain)\n", "print(\"RELATIVE IMPROVEMENT :\", relative_gain)" ] }, { "cell_type": "code", "execution_count": 8, "id": "28f02823-483c-4ed8-bf24-912c5ca7f7d3", "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1AAAAHDCAYAAAAqdvv1AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAf1BJREFUeJzs3Xd4FXXaxvHvnHQCSQikQgihJ7RAKAm9BCJNUCwoqwgoroKKWLGgWBfWio3XXQVUUMFVVFQggvRI772EThIgpELqmfePmCMxgEETTsr9uS6va8/M78x5ZvKcbG5m5jeGaZomIiIiIiIi8qcs9i5ARERERESkolCAEhERERERKSEFKBERERERkRJSgBIRERERESkhBSgREREREZESUoASEREREREpIQUoERERERGRElKAEhERERERKSEFKBERERERkRJSgBIRkVJx1113Ub9+fXuXIX9Tjx496NGjh73LKBWHDx/GMAxmzpz5l94/c+ZMDMPg8OHDpVqXiFRsClAiYnfbt2/npptuIjg4GFdXV+rUqUOfPn1455137F2aiMhfMmfOHN566y17lyEiZUABSkTsas2aNbRr146tW7dyzz338O6773L33XdjsVh4++237V2eXIX//Oc/7N27195liJQLClAilZejvQsQkart5ZdfxtPTk/Xr1+Pl5VVkXVJS0jWt5fz581SrVu2afmZlkJmZibu7O05OTvYupVSoD0RE5Ep0BkpE7OrgwYM0b968WHgC8PX1Lbbss88+o0OHDlSrVo2aNWvSrVs3Fi9eXGTM+++/T/PmzXFxcSEwMJCxY8eSkpJSZEyPHj1o0aIFGzdupFu3blSrVo2nnnoKgOzsbJ577jkaNWqEi4sLQUFBPP7442RnZ5don9auXUv//v2pWbMm7u7utGrVqtjZtKVLl9K1a1fc3d3x8vJi8ODB7N69u8iY559/HsMw2LdvH//4xz/w9PTEx8eHZ599FtM0OXbsGIMHD8bDwwN/f39ef/31Iu9ftmwZhmHw5Zdf8tRTT+Hv74+7uzvXX389x44dKzJ25cqV3HzzzdSrV8+2zw8//DAXLlwoMu6uu+6ievXqHDx4kP79+1OjRg2GDx9uW/fHe6C++OILIiIiqFGjBh4eHrRs2bLYsTh06BA333wz3t7eVKtWjcjISH744YdL7svcuXN5+eWXqVu3Lq6urvTu3ZsDBw4UGXv+/Hn27NnDmTNnrvBTKnClPvj2228ZMGAAgYGBuLi40LBhQ1588UXy8/Nt7582bRoODg5F+uv111/HMAwmTJhgW5afn0+NGjV44oknLlvLwIEDadCgwSXXRUVF0a5dO9vr2NhYunTpgpeXF9WrV6dp06a2uv9MSb5Df1TS78SMGTPo1asXvr6+uLi4EBYWxgcffFBse/Xr12fgwIGsWrWKDh064OrqSoMGDfjkk0+KjU1JSWH8+PEEBQXh4uJCo0aNmDJlClartdi4u+66C09PT7y8vBgxYkSx7/2V7Ny5k169euHm5kbdunV56aWXin0GlKwvevTowQ8//MCRI0cwDAPDMGzfjZycHCZNmkRERASenp64u7vTtWtXfvnllxLXKiL2pTNQImJXwcHBxMXFsWPHDlq0aHHFsZMnT+b555+nU6dOvPDCCzg7O7N27VqWLl1K3759gYLQMXnyZKKjo7nvvvvYu3cvH3zwAevXr2f16tVFzpKcPXuWfv36MWzYMP7xj3/g5+eH1Wrl+uuvZ9WqVYwZM4bQ0FC2b9/Om2++yb59+5g/f/4Va4yNjWXgwIEEBATw0EMP4e/vz+7du1mwYAEPPfQQAD///DP9+vWjQYMGPP/881y4cIF33nmHzp07s2nTpmIh5NZbbyU0NJR//etf/PDDD7z00kt4e3vzf//3f/Tq1YspU6Ywe/ZsHn30Udq3b0+3bt2KvP/ll1/GMAyeeOIJkpKSeOutt4iOjmbLli24ubkBMG/ePM6fP899991HrVq1WLduHe+88w7Hjx9n3rx5RbaXl5dHTEwMXbp04bXXXrvs2ZrY2Fhuu+02evfuzZQpUwDYvXs3q1evth2LxMREOnXqxPnz53nwwQepVasWs2bN4vrrr+err77ihhtuKLLNf/3rX1gsFh599FFSU1OZOnUqw4cPZ+3atbYx69ato2fPnjz33HM8//zzV/x5waX7AAomEKhevToTJkygevXqLF26lEmTJpGWlsa///1vALp27YrVamXVqlUMHDgQKAijFouFlStX2j5j8+bNZGRkFPvZXOzWW2/lzjvvZP369bRv3962/MiRI/z666+2z9y5cycDBw6kVatWvPDCC7i4uHDgwAFWr179p/taku/QH13Nd+KDDz6gefPmXH/99Tg6OvL9999z//33Y7Va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z4mIiDAnT55spqam/uk+rVq1yuzTp49Zo0YN093d3WzVqpX5zjvvFBnz888/m507dzbd3NxMDw8Pc9CgQeauXbuKjCmcxvyP01WPGDHCdHd3L/a5f9ynwum8P//8c3PixImmr6+v6ebmZg4YMKDI1OSmaZq7du0yo6OjzerVq5u1a9c277nnHnPr1q3FpoC+3GcXrrt42uavvvrK7Nu3r+nr62s6Ozub9erVM++9917z1KlTRd538OBB86abbjK9vLxMV1dXs0OHDuaCBQuKjCncl3nz5hVZHh8fX6zGq53G/HJ9sHr1ajMyMtJ0c3MzAwMDbVPsA+Yvv/xSZOwPP/xgAma/fv2KLL/77rtNwPzoo4/+tJaLDR8+3ATM6OjoYuuWLFliDh482AwMDDSdnZ3NwMBA87bbbis21ffl/Nl36I/TmJtmyb8T3333ndmqVSvT1dXVrF+/vjllyhTz448/LjYVeHBwsDlgwIBitV3qs9PT082JEyeajRo1Mp2dnc3atWubnTp1Ml977bUijzs4e/aseccdd5geHh6mp6eneccdd5ibN28u0TTmpmma27ZtM7t37266urqaderUMV988UXzo48+KlZ7SfsiIyPDvP32200vLy8TsH03rFar+corr5jBwcGmi4uL2aZNG3PBggXFvj8iUn4Zpvk37rwVEZFya9myZfTs2ZN58+Zx00032bscERGRSkH3QImIiIiIiJSQApSIiIiIiEgJKUCJiIiIiIiUkO6BEhERERERKSGdgRIRERERESkhBSgREREREZESqtIP0rVarZw8eZIaNWpgGIa9yxERERERETsxTZP09HQCAwOv+CD3Kh2gTp48SVBQkL3LEBERERGRcuLYsWPUrVv3suurdICqUaMGUHCQPDw87FxN1WK1Wjl9+jQ+Pj5XTPhS+akXpJB6QQqpF6SQekEuVtb9kJaWRlBQkC0jXE6VDlCFl+15eHgoQF1jVquVrKwsPDw89AuxilMvSCH1ghRSL0gh9YJc7Fr1w5/d2qNOFBERERERKSEFKBERERERkRJSgBIRERERESmhKn0PVElYrVZycnLsXUal4+DgYO8SRERERESumgLUFeTk5BAfH4/VarV3KZWOaZo4OTnh4+Nj71JEREREREpMAeoyTNPk1KlTODg4EBQUpJlfSpFpmmRmZpKQkEBiYiKBgYH2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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.figure(figsize=(10,5))\n", "\n", "plt.plot(comparison[\"date\"], comparison[\"raw_scores\"], label=\"Raw\")\n", "plt.plot(comparison[\"date\"], comparison[\"clean_scores\"], label=\"Clean\")\n", "\n", "plt.legend()\n", "plt.title(\"Score comparison: raw vs cleaned data\")\n", "plt.grid(alpha=0.3)\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 14, "id": "a002234f-deec-4283-8245-ffac74f1930b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[None]" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import os\n", "import s3fs\n", "os.environ[\"AWS_ACCESS_KEY_ID\"] = 'N0C5PK75FDX2WXI8OVP1'\n", "os.environ[\"AWS_SECRET_ACCESS_KEY\"] = 'nZvC2urUkG7EvhDsFDyaOslqr160aoWMs+5MP3Ft'\n", "os.environ[\"AWS_SESSION_TOKEN\"] = 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJOMEM1UEs3NUZEWDJXWEk4T1ZQMSIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJhdWQiOlsibWluaW8iLCJhY2NvdW50Il0sImF1dGhfdGltZSI6MTc3MzIyNzI3OCwiYXpwIjoib255eGlhLW1pbmlvIiwiZW1haWwiOiJzYXJhaC50aG91bXlyZUBlbnNhZS5mciIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE3NzQ0MzY4OTksImZhbWlseV9uYW1lIjoiVEhPVU1ZUkUiLCJnaXZlbl9uYW1lIjoiU2FyYWgiLCJncm91cHMiOlsiYmRjLWRhdGEiLCJiZGMtY2FybWlnbmFjLWczIl0sImlhdCI6MTc3MzIyNzI5OSwiaXNzIjoiaHR0cHM6Ly9hdXRoLmdyb3VwZS1nZW5lcy5mci9yZWFsbXMvZ2VuZXMiLCJqdGkiOiI5Mjc0ODgyMy04OTgzLTQzYjktYTZhNy0xYjhlNDdiOTRjNTUiLCJuYW1lIjoiU2FyYWggVEhPVU1ZUkUiLCJwb2xpY3kiOiJzdHNvbmx5IiwicHJlZmVycmVkX3VzZXJuYW1lIjoic3Rob3VteXJlLWVuc2FlIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1nZW5lcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6IjRkODM3NWVmLTQwY2QtNDYyMi05NzIyLTI4YjhjZTQ2MWQ5YyIsInN1YiI6ImVhYWVkN2QyLWM4MjYtNGIxNC05MzczLTYwYjNhODhlMWFiNiIsInR5cCI6IkJlYXJlciJ9.hl_SekvaH9A22PMb3W0VQBSNO67LnaneIuLC-X5XBnzOO5GLV61aocDRfYC6hvVVhdzyewSTtD2kvdyJdeu6qA'\n", "os.environ[\"AWS_DEFAULT_REGION\"] = 'us-east-1'\n", "fs = s3fs.S3FileSystem(\n", " client_kwargs={'endpoint_url': 'https://'+'minio-simple.lab.groupe-genes.fr'},\n", " key = os.environ[\"AWS_ACCESS_KEY_ID\"], \n", " secret = os.environ[\"AWS_SECRET_ACCESS_KEY\"], \n", " token = os.environ[\"AWS_SESSION_TOKEN\"])\n", "\n", "# 3️⃣ Upload du CSV\n", "local_file = \"stock_repaired.csv\"\n", "s3_path = \"projet-bdc-carmignac-g3\"\n", "\n", "fs.put(local_file, s3_path)" ] }, { "cell_type": "code", "execution_count": 16, "id": "eeb8f32c-c717-4d48-85c5-248661b5a945", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "====================================\n", "RUNNING COHERENCE SCORE ON: raw\n", "====================================\n", "GLOBAL SCORE: 0.756699778421513\n", "\n", "====================================\n", "RUNNING COHERENCE SCORE ON: clean\n", "====================================\n" ] }, { "ename": "KeyError", "evalue": "'Centralisation Date'", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/indexes/base.py:3812\u001b[39m, in \u001b[36mIndex.get_loc\u001b[39m\u001b[34m(self, key)\u001b[39m\n\u001b[32m 3811\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m3812\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_engine\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcasted_key\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 3813\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/index.pyx:167\u001b[39m, in \u001b[36mpandas._libs.index.IndexEngine.get_loc\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/index.pyx:196\u001b[39m, in \u001b[36mpandas._libs.index.IndexEngine.get_loc\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/hashtable_class_helper.pxi:7088\u001b[39m, in \u001b[36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/hashtable_class_helper.pxi:7096\u001b[39m, in \u001b[36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[39m\u001b[34m()\u001b[39m\n", "\u001b[31mKeyError\u001b[39m: 'Centralisation Date'", "\nThe above exception was the direct cause of the following exception:\n", "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[16]\u001b[39m\u001b[32m, line 45\u001b[39m\n\u001b[32m 39\u001b[39m \u001b[38;5;66;03m# --------------------------------------------------------\u001b[39;00m\n\u001b[32m 40\u001b[39m \u001b[38;5;66;03m# LOAD STOCKS\u001b[39;00m\n\u001b[32m 41\u001b[39m \u001b[38;5;66;03m# --------------------------------------------------------\u001b[39;00m\n\u001b[32m 43\u001b[39m stocks = pd.read_csv(stock_file, low_memory=\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[32m---> \u001b[39m\u001b[32m45\u001b[39m stocks[\u001b[33m\"\u001b[39m\u001b[33mCentralisation Date\u001b[39m\u001b[33m\"\u001b[39m] = pd.to_datetime(\u001b[43mstocks\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mCentralisation Date\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m)\n\u001b[32m 46\u001b[39m stocks[\u001b[33m\"\u001b[39m\u001b[33mRegistrar Account - ID\u001b[39m\u001b[33m\"\u001b[39m] = stocks[\u001b[33m\"\u001b[39m\u001b[33mRegistrar Account - ID\u001b[39m\u001b[33m\"\u001b[39m].astype(\u001b[38;5;28mstr\u001b[39m).str.strip()\n\u001b[32m 47\u001b[39m stocks[\u001b[33m\"\u001b[39m\u001b[33mProduct - Isin\u001b[39m\u001b[33m\"\u001b[39m] = stocks[\u001b[33m\"\u001b[39m\u001b[33mProduct - Isin\u001b[39m\u001b[33m\"\u001b[39m].astype(\u001b[38;5;28mstr\u001b[39m)\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/frame.py:4113\u001b[39m, in \u001b[36mDataFrame.__getitem__\u001b[39m\u001b[34m(self, key)\u001b[39m\n\u001b[32m 4111\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.columns.nlevels > \u001b[32m1\u001b[39m:\n\u001b[32m 4112\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._getitem_multilevel(key)\n\u001b[32m-> \u001b[39m\u001b[32m4113\u001b[39m indexer = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mcolumns\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 4114\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m is_integer(indexer):\n\u001b[32m 4115\u001b[39m indexer = [indexer]\n", "\u001b[36mFile \u001b[39m\u001b[32m/opt/python/lib/python3.13/site-packages/pandas/core/indexes/base.py:3819\u001b[39m, in \u001b[36mIndex.get_loc\u001b[39m\u001b[34m(self, key)\u001b[39m\n\u001b[32m 3814\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(casted_key, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m (\n\u001b[32m 3815\u001b[39m \u001b[38;5;28misinstance\u001b[39m(casted_key, abc.Iterable)\n\u001b[32m 3816\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28many\u001b[39m(\u001b[38;5;28misinstance\u001b[39m(x, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;28;01mfor\u001b[39;00m x \u001b[38;5;129;01min\u001b[39;00m casted_key)\n\u001b[32m 3817\u001b[39m ):\n\u001b[32m 3818\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m InvalidIndexError(key)\n\u001b[32m-> \u001b[39m\u001b[32m3819\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01merr\u001b[39;00m\n\u001b[32m 3820\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[32m 3821\u001b[39m \u001b[38;5;66;03m# If we have a listlike key, _check_indexing_error will raise\u001b[39;00m\n\u001b[32m 3822\u001b[39m \u001b[38;5;66;03m# InvalidIndexError. Otherwise we fall through and re-raise\u001b[39;00m\n\u001b[32m 3823\u001b[39m \u001b[38;5;66;03m# the TypeError.\u001b[39;00m\n\u001b[32m 3824\u001b[39m \u001b[38;5;28mself\u001b[39m._check_indexing_error(key)\n", "\u001b[31mKeyError\u001b[39m: 'Centralisation Date'" ] } ], "source": [ "# ============================================================\n", "# DATASETS\n", "# ============================================================\n", "\n", "DATASETS = {\n", " \"raw\": \"stocks.csv\",\n", "}\n", "\n", "flows_file = \"flows.csv\"\n", "\n", "results = {}\n", "\n", "# ============================================================\n", "# LOAD FLOWS\n", "# ============================================================\n", "\n", "flows = pd.read_csv(flows_file, low_memory=False)\n", "\n", "flows[\"Centralisation Date\"] = pd.to_datetime(flows[\"Centralisation Date\"])\n", "flows[\"Registrar Account - ID\"] = flows[\"Registrar Account - ID\"].astype(str).str.strip()\n", "flows[\"Product - Isin\"] = flows[\"Product - Isin\"].astype(str)\n", "\n", "flows[\"Quantity - NetFlows\"] = flows[\"Quantity - NetFlows\"].fillna(0)\n", "\n", "# ============================================================\n", "# LOOP OVER DATASETS\n", "# ============================================================\n", "\n", "for name, stock_file in DATASETS.items():\n", "\n", " print(\"\\n====================================\")\n", " print(\"RUNNING COHERENCE SCORE ON:\", name)\n", " print(\"====================================\")\n", "\n", " # --------------------------------------------------------\n", " # LOAD STOCKS\n", " # --------------------------------------------------------\n", "\n", " stocks = pd.read_csv(stock_file, low_memory=False)\n", "\n", " stocks[\"Centralisation Date\"] = pd.to_datetime(stocks[\"Centralisation Date\"])\n", " stocks[\"Registrar Account - ID\"] = stocks[\"Registrar Account - ID\"].astype(str).str.strip()\n", " stocks[\"Product - Isin\"] = stocks[\"Product - Isin\"].astype(str)\n", "\n", " # --------------------------------------------------------\n", " # MERGE FLOWS\n", " # --------------------------------------------------------\n", "\n", " df = stocks.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " )\n", "\n", " # --------------------------------------------------------\n", " # ACCOUNTING RELATION\n", " # --------------------------------------------------------\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " ).fillna(0)\n", "\n", " df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", " df[\"error\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", " # --------------------------------------------------------\n", " # NORMALIZED ERROR\n", " # --------------------------------------------------------\n", "\n", " df[\"scale\"] = df[\"prev_aum\"].abs().clip(lower=1)\n", "\n", " df[\"normalized_error\"] = df[\"error\"].abs() / df[\"scale\"]\n", "\n", " # --------------------------------------------------------\n", " # OBSERVATION SCORE\n", " # --------------------------------------------------------\n", "\n", " df[\"score_obs\"] = np.exp(-5 * df[\"normalized_error\"])\n", "\n", " # --------------------------------------------------------\n", " # ACCOUNT SCORE\n", " # --------------------------------------------------------\n", "\n", " account_score = (\n", " df.groupby(\"Registrar Account - ID\")[\"score_obs\"]\n", " .mean()\n", " )\n", "\n", " # --------------------------------------------------------\n", " # ACCOUNT WEIGHTS (31/10/2025)\n", " # --------------------------------------------------------\n", "\n", " last_date = stocks[\"Centralisation Date\"].max()\n", "\n", " aum_last = (\n", " stocks[stocks[\"Centralisation Date\"] == last_date]\n", " .groupby(\"Registrar Account - ID\")[\"Quantity - AUM\"]\n", " .sum()\n", " )\n", "\n", " weights = aum_last / aum_last.sum()\n", "\n", " # --------------------------------------------------------\n", " # ALIGN\n", " # --------------------------------------------------------\n", "\n", " combined = pd.concat([account_score, weights], axis=1)\n", " combined.columns = [\"score\", \"weight\"]\n", "\n", " combined = combined.fillna(0)\n", "\n", " # --------------------------------------------------------\n", " # GLOBAL SCORE\n", " # --------------------------------------------------------\n", "\n", " combined[\"weighted_score\"] = combined[\"score\"] * combined[\"weight\"]\n", "\n", " global_score = combined[\"weighted_score\"].sum()\n", "\n", " print(\"GLOBAL SCORE:\", global_score)\n", "\n", " results[name] = {\n", " \"score\": global_score,\n", " \"details\": combined\n", " }\n", "\n", "# ============================================================\n", "# COMPARISON\n", "# ============================================================\n", "\n", "print(\"\\n====================================\")\n", "print(\"RAW VS CLEAN COMPARISON\")\n", "print(\"====================================\")\n", "\n", "raw_score = results[\"raw\"][\"score\"]\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "ca380a04-155e-4fea-bfae-cfbfa9401245", "metadata": {}, "outputs": [], "source": [ "flows = pd.read_csv(flows_file, sep=None, engine=\"python\")\n", "stocks = pd.read_csv(stock_file, sep=None, engine=\"python\")" ] }, { "cell_type": "code", "execution_count": 23, "id": "f515fcd8-872b-40c5-8a5b-4eacf5e51097", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Index(['Unnamed: 0', 'Agreement - Code', 'Company - Id',\n", " 'Company - Ultimate Parent Id', 'Registrar Account - ID',\n", " 'Registrar Account - Region', 'RegistrarAccount - Country',\n", " 'Product - Asset Type', 'Product - Strategy', 'Product - Legal Status',\n", " 'Product - Is Dedie ?', 'Product - Fund', 'Product - Shareclass Type',\n", " 'Product - Shareclass Currency', 'Product - Isin',\n", " 'Centralisation Date', 'Quantity - Subscription',\n", " 'Quantity - Redemption', 'Quantity - NetFlows',\n", " 'Value Ccy - Subscription', 'Value Ccy - Redemption',\n", " 'Value Ccy - NetFlows', 'Value € - Subscription',\n", " 'Value € - Redemption', 'Value € - NetFlows'],\n", " dtype='object')\n", "Index(['Unnamed: 0', 'Agreement - Code', 'Company - Id',\n", " 'Company - Ultimate Parent Id', 'Registrar Account - ID',\n", " 'Registrar Account - Region', 'RegistrarAccount - Country',\n", " 'Product - Asset Type', 'Product - Strategy', 'Product - Legal Status',\n", " 'Product - Is Dedie ?', 'Product - Fund', 'Product - Shareclass Type',\n", " 'Product - Shareclass Currency', 'Product - Isin',\n", " 'Centralisation Date', 'Quantity - AUM', 'Value - AUM CCY',\n", " 'Value - AUM €'],\n", " dtype='object')\n" ] } ], "source": [ "print(flows.columns)\n", "print(stocks.columns)" ] }, { "cell_type": "code", "execution_count": 24, "id": "4ce0bfea-7714-4fd7-ba29-17ce8d976ab6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "====================================\n", "RUNNING COHERENCE SCORE ON: raw\n", "====================================\n", "GLOBAL SCORE: 0.756699778421513\n", "\n", "====================================\n", "RUNNING COHERENCE SCORE ON: clean\n", "====================================\n", "GLOBAL SCORE: 0.756699778421513\n", "\n", "====================================\n", "RAW VS CLEAN COMPARISON\n", "====================================\n", "RAW SCORE : 0.756699778421513\n", "CLEAN SCORE : 0.756699778421513\n", "IMPROVEMENT : 0.0\n", "RELATIVE IMPROVEMENT : 0.0\n" ] } ], "source": [ "import pandas as pd\n", "import numpy as np\n", "\n", "# ============================================================\n", "# DATASETS\n", "# ============================================================\n", "\n", "DATASETS = {\n", " \"raw\": \"stocks.csv\", \n", " \"clean\": \"stock_repaired.csv\"\n", "}\n", "\n", "flows_file = \"flows.csv\"\n", "\n", "results = {}\n", "\n", "# ============================================================\n", "# LOAD FLOWS\n", "# ============================================================\n", "\n", "flows[\"Centralisation Date\"] = pd.to_datetime(flows[\"Centralisation Date\"])\n", "flows[\"Registrar Account - ID\"] = flows[\"Registrar Account - ID\"].astype(str).str.strip()\n", "flows[\"Product - Isin\"] = flows[\"Product - Isin\"].astype(str)\n", "\n", "flows[\"Quantity - NetFlows\"] = flows[\"Quantity - NetFlows\"].fillna(0)\n", "\n", "# ============================================================\n", "# LOOP OVER DATASETS\n", "# ============================================================\n", "\n", "for name, stock_file in DATASETS.items():\n", "\n", " print(\"\\n====================================\")\n", " print(\"RUNNING COHERENCE SCORE ON:\", name)\n", " print(\"====================================\")\n", "\n", " # --------------------------------------------------------\n", " # LOAD STOCKS\n", " # --------------------------------------------------------\n", "\n", " stocks[\"Centralisation Date\"] = pd.to_datetime(stocks[\"Centralisation Date\"])\n", " stocks[\"Registrar Account - ID\"] = stocks[\"Registrar Account - ID\"].astype(str).str.strip()\n", " stocks[\"Product - Isin\"] = stocks[\"Product - Isin\"].astype(str)\n", "\n", " # --------------------------------------------------------\n", " # MERGE FLOWS\n", " # --------------------------------------------------------\n", "\n", " df = stocks.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " )\n", "\n", " # --------------------------------------------------------\n", " # ACCOUNTING RELATION\n", " # --------------------------------------------------------\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " ).fillna(0)\n", "\n", " df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", " df[\"error\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", " # --------------------------------------------------------\n", " # NORMALIZED ERROR\n", " # --------------------------------------------------------\n", "\n", " df[\"scale\"] = df[\"prev_aum\"].abs().clip(lower=1)\n", "\n", " df[\"normalized_error\"] = df[\"error\"].abs() / df[\"scale\"]\n", "\n", " # --------------------------------------------------------\n", " # OBSERVATION SCORE\n", " # --------------------------------------------------------\n", "\n", " df[\"score_obs\"] = np.exp(-5 * df[\"normalized_error\"])\n", "\n", " # --------------------------------------------------------\n", " # ACCOUNT SCORE\n", " # --------------------------------------------------------\n", "\n", " account_score = (\n", " df.groupby(\"Registrar Account - ID\")[\"score_obs\"]\n", " .mean()\n", " )\n", "\n", " # --------------------------------------------------------\n", " # ACCOUNT WEIGHTS (31/10/2025)\n", " # --------------------------------------------------------\n", "\n", " last_date = stocks[\"Centralisation Date\"].max()\n", "\n", " aum_last = (\n", " stocks[stocks[\"Centralisation Date\"] == last_date]\n", " .groupby(\"Registrar Account - ID\")[\"Quantity - AUM\"]\n", " .sum()\n", " )\n", "\n", " weights = aum_last / aum_last.sum()\n", "\n", " combined = pd.concat([account_score, weights], axis=1)\n", " combined.columns = [\"score\", \"weight\"]\n", " combined = combined.fillna(0)\n", "\n", " combined[\"weighted_score\"] = combined[\"score\"] * combined[\"weight\"]\n", "\n", " global_score = combined[\"weighted_score\"].sum()\n", "\n", " print(\"GLOBAL SCORE:\", global_score)\n", "\n", " results[name] = global_score\n", "\n", "# ============================================================\n", "# COMPARISON\n", "# ============================================================\n", "\n", "print(\"\\n====================================\")\n", "print(\"RAW VS CLEAN COMPARISON\")\n", "print(\"====================================\")\n", "\n", "raw_score = results[\"raw\"]\n", "clean_score = results[\"clean\"]\n", "\n", "improvement = clean_score - raw_score\n", "relative = improvement / raw_score\n", "\n", "print(\"RAW SCORE :\", raw_score)\n", "print(\"CLEAN SCORE :\", clean_score)\n", "print(\"IMPROVEMENT :\", improvement)\n", "print(\"RELATIVE IMPROVEMENT :\", relative)" ] }, { "cell_type": "code", "execution_count": 26, "id": "d5d2a309-f843-47ff-b256-66e7d4843c0b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TRAJECTORY STABILITY SCORE : 0.7577041546254711\n" ] } ], "source": [ "# ============================================================\n", "# TRAJECTORY STABILITY SCORE\n", "# ============================================================\n", "\n", "# merge stocks + flows\n", "df = stocks.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", ")\n", "\n", "df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", "df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", ")\n", "\n", "# ------------------------------------------------------------\n", "# COMPUTE DELTA AUM\n", "# ------------------------------------------------------------\n", "\n", "df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", ")\n", "\n", "df[\"delta_aum\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", "# ------------------------------------------------------------\n", "# TRAJECTORY ERROR\n", "# ------------------------------------------------------------\n", "\n", "df[\"trajectory_error\"] = df[\"delta_aum\"] - df[\"Quantity - NetFlows\"]\n", "\n", "# normalize relative to portfolio size\n", "df[\"scale\"] = df[\"prev_aum\"].abs().clip(lower=1)\n", "\n", "df[\"normalized_error\"] = df[\"trajectory_error\"].abs() / df[\"scale\"]\n", "\n", "# ------------------------------------------------------------\n", "# SCORE PER OBSERVATION\n", "# ------------------------------------------------------------\n", "\n", "df[\"score_obs\"] = np.exp(-5 * df[\"normalized_error\"])\n", "\n", "# ------------------------------------------------------------\n", "# SCORE PER DISTRIBUTOR\n", "# ------------------------------------------------------------\n", "\n", "account_score = (\n", " df.groupby(\"Registrar Account - ID\")[\"score_obs\"]\n", " .mean()\n", ")\n", "\n", "# ------------------------------------------------------------\n", "# WEIGHTS (AUM LAST DATE)\n", "# ------------------------------------------------------------\n", "\n", "last_date = stocks[\"Centralisation Date\"].max()\n", "\n", "aum_last = (\n", " stocks[stocks[\"Centralisation Date\"] == last_date]\n", " .groupby(\"Registrar Account - ID\")[\"Quantity - AUM\"]\n", " .sum()\n", ")\n", "\n", "weights = aum_last / aum_last.sum()\n", "\n", "# ------------------------------------------------------------\n", "# GLOBAL SCORE\n", "# ------------------------------------------------------------\n", "\n", "combined = pd.concat([account_score, weights], axis=1)\n", "combined.columns = [\"score\", \"weight\"]\n", "combined = combined.fillna(0)\n", "\n", "combined[\"weighted_score\"] = combined[\"score\"] * combined[\"weight\"]\n", "\n", "trajectory_score = combined[\"weighted_score\"].sum()\n", "\n", "print(\"TRAJECTORY STABILITY SCORE :\", trajectory_score)" ] }, { "cell_type": "code", "execution_count": 27, "id": "af46b28d-957c-44d8-b46c-fc3e8a76fdd0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MEAN |ΔAUM − Flow| RAW : 1279.1238370039505\n", "MEAN |ΔAUM − Flow| CLEAN : 1324.7974438780602\n", "ABSOLUTE IMPROVEMENT : -45.67360687410974\n", "RELATIVE IMPROVEMENT : -0.03570694685910124\n" ] } ], "source": [ "# ============================================================\n", "# MEAN ABSOLUTE TRAJECTORY ERROR\n", "# ============================================================\n", "\n", "def compute_mean_error(stocks_df):\n", "\n", " df = stocks_df.merge(\n", " flows,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " )\n", "\n", " # previous AUM\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " # delta AUM\n", " df[\"delta_aum\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", " # trajectory error\n", " df[\"trajectory_error\"] = df[\"delta_aum\"] - df[\"Quantity - NetFlows\"]\n", "\n", " # absolute error\n", " df[\"abs_error\"] = df[\"trajectory_error\"].abs()\n", "\n", " return df[\"abs_error\"].mean()\n", "\n", "\n", "# ------------------------------------------------------------\n", "# COMPUTE RAW VS CLEAN\n", "# ------------------------------------------------------------\n", "\n", "raw_error = compute_mean_error(stocks)\n", "clean_error = compute_mean_error(stocks_repaired)\n", "\n", "print(\"MEAN |ΔAUM − Flow| RAW :\", raw_error)\n", "print(\"MEAN |ΔAUM − Flow| CLEAN :\", clean_error)\n", "\n", "improvement = raw_error - clean_error\n", "relative = improvement / raw_error\n", "\n", "print(\"ABSOLUTE IMPROVEMENT :\", improvement)\n", "print(\"RELATIVE IMPROVEMENT :\", relative)" ] }, { "cell_type": "code", "execution_count": 30, "id": "70938ac0-f9c3-47d2-b4d7-cdefbee72add", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MEAN RELATIVE |ΔAUM| RAW : 0.382395981437048\n", "MEAN RELATIVE |ΔAUM| CLEAN : 0.3956454388801965\n", "VAR RELATIVE ΔAUM RAW : 55.51684717820779\n", "VAR RELATIVE ΔAUM CLEAN : 55.8099150101412\n", "\n", "IMPROVEMENTS\n", "MEAN IMPROVEMENT : -0.013249457443148482\n", "VAR IMPROVEMENT : -0.29306783193340635\n" ] } ], "source": [ "# ============================================================\n", "# NORMALIZED AUM STABILITY (ROBUST VERSION)\n", "# ============================================================\n", "\n", "def compute_aum_stability_filtered(stocks_df, min_aum=1000):\n", "\n", " df = stocks_df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " ).copy()\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"delta_aum\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", " # filter small portfolios\n", " df = df[df[\"prev_aum\"].abs() > min_aum]\n", "\n", " df[\"normalized_delta\"] = df[\"delta_aum\"].abs() / df[\"prev_aum\"].abs()\n", "\n", " mean_norm_delta = df[\"normalized_delta\"].mean()\n", " var_norm_delta = df[\"normalized_delta\"].var()\n", "\n", " return mean_norm_delta, var_norm_delta\n", "\n", "\n", "# ------------------------------------------------------------\n", "# RAW\n", "# ------------------------------------------------------------\n", "\n", "raw_mean, raw_var = compute_aum_stability_filtered(stocks)\n", "\n", "# ------------------------------------------------------------\n", "# CLEAN\n", "# ------------------------------------------------------------\n", "\n", "clean_mean, clean_var = compute_aum_stability_filtered(stocks_repaired)\n", "\n", "# ------------------------------------------------------------\n", "# RESULTS\n", "# ------------------------------------------------------------\n", "\n", "print(\"MEAN RELATIVE |ΔAUM| RAW :\", raw_mean)\n", "print(\"MEAN RELATIVE |ΔAUM| CLEAN :\", clean_mean)\n", "\n", "print(\"VAR RELATIVE ΔAUM RAW :\", raw_var)\n", "print(\"VAR RELATIVE ΔAUM CLEAN :\", clean_var)\n", "\n", "print(\"\\nIMPROVEMENTS\")\n", "\n", "print(\"MEAN IMPROVEMENT :\", raw_mean - clean_mean)\n", "print(\"VAR IMPROVEMENT :\", raw_var - clean_var)" ] }, { "cell_type": "code", "execution_count": 34, "id": "9d89bfeb-db8c-4c3c-af6e-fee7ea524dbd", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of large accounts: 433\n", "\n", "RAW DATA\n", "mean_jump : 224994.9664587711\n", "median_jump : 0.01670204997155296\n", "jump>50% : 0.13255873973605017\n", "jump>100% : 0.06362709256786046\n", "jump>200% : 0.0557404201046879\n", "\n", "CLEAN DATA\n", "mean_jump : 93408.81311850502\n", "median_jump : 0.026516611646358403\n", "jump>50% : 0.15272455736996207\n", "jump>100% : 0.06512212855430446\n", "jump>200% : 0.05549332861718643\n", "\n", "IMPROVEMENTS\n", "mean_jump : 131586.1533402661\n", "median_jump : -0.009814561674805444\n", "jump>50% : -0.020165817633911898\n", "jump>100% : -0.0014950359864440016\n", "jump>200% : 0.0002470914875014746\n" ] } ], "source": [ "# ============================================================\n", "# CLEANING QUALITY TEST\n", "# ============================================================\n", "\n", "# ------------------------------------------------------------\n", "# STEP 1 — SELECT LARGE ACCOUNTS (>5M€ at last date)\n", "# ------------------------------------------------------------\n", "\n", "last_date = stocks[\"Centralisation Date\"].max()\n", "\n", "aum_last = (\n", " stocks[stocks[\"Centralisation Date\"] == last_date]\n", " .groupby(\"Registrar Account - ID\")[\"Value - AUM €\"]\n", " .sum()\n", ")\n", "\n", "large_accounts = aum_last[aum_last >= 5_000_000].index\n", "\n", "print(\"Number of large accounts:\", len(large_accounts))\n", "\n", "stocks_large = stocks[\n", " stocks[\"Registrar Account - ID\"].isin(large_accounts)\n", "]\n", "\n", "stocks_repaired_large = stocks_repaired[\n", " stocks_repaired[\"Registrar Account - ID\"].isin(large_accounts)\n", "]\n", "\n", "\n", "# ------------------------------------------------------------\n", "# STEP 2 — COMPUTE RELATIVE AUM JUMPS\n", "# ------------------------------------------------------------\n", "\n", "def compute_aum_jumps(df):\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " ).copy()\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Value - AUM €\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"relative_jump\"] = (\n", " (df[\"Value - AUM €\"] - df[\"prev_aum\"]).abs() /\n", " df[\"prev_aum\"].abs().clip(lower=1)\n", " )\n", "\n", " return df\n", "\n", "\n", "raw_df = compute_aum_jumps(stocks_large)\n", "clean_df = compute_aum_jumps(stocks_repaired_large)\n", "\n", "\n", "# ------------------------------------------------------------\n", "# STEP 3 — METRICS\n", "# ------------------------------------------------------------\n", "\n", "def summarize_jumps(df):\n", "\n", " jumps = df[\"relative_jump\"].dropna()\n", "\n", " results = {\n", " \"mean_jump\": jumps.mean(),\n", " \"median_jump\": jumps.median(),\n", " \"jump>50%\": (jumps > 0.5).mean(),\n", " \"jump>100%\": (jumps > 1).mean(),\n", " \"jump>200%\": (jumps > 2).mean()\n", " }\n", "\n", " return results\n", "\n", "\n", "raw_stats = summarize_jumps(raw_df)\n", "clean_stats = summarize_jumps(clean_df)\n", "\n", "\n", "# ------------------------------------------------------------\n", "# STEP 4 — PRINT RESULTS\n", "# ------------------------------------------------------------\n", "\n", "print(\"\\nRAW DATA\")\n", "for k, v in raw_stats.items():\n", " print(k, \":\", v)\n", "\n", "print(\"\\nCLEAN DATA\")\n", "for k, v in clean_stats.items():\n", " print(k, \":\", v)\n", "\n", "print(\"\\nIMPROVEMENTS\")\n", "\n", "for k in raw_stats:\n", " print(k, \":\", raw_stats[k] - clean_stats[k])" ] }, { "cell_type": "code", "execution_count": 35, "id": "c6a4042e-85e4-476c-8fdb-daac82caa896", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OBSERVATIONS CORRECTED : 32537\n", "SERIES CORRECTED : 297\n", "TOTAL AUM ADJUSTMENT : 390742193.9398935\n", "MEAN ADJUSTMENT (corrected points) : 12009.164764418769\n" ] } ], "source": [ "# ============================================================\n", "# CLEANING IMPACT METRICS\n", "# ============================================================\n", "\n", "# merge raw vs cleaned\n", "df_compare = stocks.merge(\n", " stocks_repaired,\n", " on=[\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\"\n", " ],\n", " suffixes=(\"_raw\", \"_clean\")\n", ")\n", "\n", "# difference in AUM\n", "df_compare[\"aum_diff\"] = (\n", " df_compare[\"Quantity - AUM_clean\"] -\n", " df_compare[\"Quantity - AUM_raw\"]\n", ")\n", "\n", "# ------------------------------------------------------------\n", "# number of observations corrected\n", "# ------------------------------------------------------------\n", "\n", "obs_corrected = (df_compare[\"aum_diff\"] != 0).sum()\n", "\n", "# ------------------------------------------------------------\n", "# number of series corrected\n", "# ------------------------------------------------------------\n", "\n", "series_corrected = (\n", " df_compare[df_compare[\"aum_diff\"] != 0]\n", " .groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " .ngroups\n", ")\n", "\n", "# ------------------------------------------------------------\n", "# magnitude of corrections\n", "# ------------------------------------------------------------\n", "\n", "total_adjustment = df_compare[\"aum_diff\"].abs().sum()\n", "\n", "mean_adjustment = (\n", " df_compare.loc[df_compare[\"aum_diff\"] != 0, \"aum_diff\"]\n", " .abs()\n", " .mean()\n", ")\n", "\n", "# ------------------------------------------------------------\n", "# results\n", "# ------------------------------------------------------------\n", "\n", "print(\"OBSERVATIONS CORRECTED :\", obs_corrected)\n", "print(\"SERIES CORRECTED :\", series_corrected)\n", "\n", "print(\"TOTAL AUM ADJUSTMENT :\", total_adjustment)\n", "print(\"MEAN ADJUSTMENT (corrected points) :\", mean_adjustment)" ] }, { "cell_type": "code", "execution_count": 42, "id": "3bd29760-baa3-4916-a0d1-40ea6c898fb8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "RAW SCORE : 8.867398273073611e-11\n", "CLEAN SCORE : 1.9555471695728966e-10\n", "IMPROVEMENT : 1.0688073422655355e-10\n", "2.205322361025593\n" ] } ], "source": [ "# ============================================================\n", "# SCORE 2 — TRAJECTORY STABILITY\n", "# ============================================================\n", "\n", "def compute_stability_score(df):\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " ).copy()\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"delta\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", " score = 1 / (1 + df[\"delta\"].var())\n", "\n", " return score\n", "\n", "raw_score2 = compute_stability_score(stocks_large)\n", "clean_score2 = compute_stability_score(stocks_repaired_large)\n", "\n", "print(\"RAW SCORE :\", raw_score2)\n", "print(\"CLEAN SCORE :\", clean_score2)\n", "print(\"IMPROVEMENT :\", clean_score2 - raw_score2)\n", "\n", "improvement = clean_score2 / raw_score2\n", "print(improvement)" ] }, { "cell_type": "code", "execution_count": 39, "id": "ba38f855-7b3a-4438-84e8-96b0c37892ef", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of large accounts: 433\n", "stocks_large rows: 810442\n", "flows_large rows: 1529403\n" ] } ], "source": [ "# ============================================================\n", "# CREATE FLOWS_LARGE (same filter as stocks_large)\n", "# ============================================================\n", "\n", "# last date\n", "last_date = stocks[\"Centralisation Date\"].max()\n", "\n", "# AUM per account at last date (in euros)\n", "aum_last = (\n", " stocks[stocks[\"Centralisation Date\"] == last_date]\n", " .groupby(\"Registrar Account - ID\")[\"Value - AUM €\"]\n", " .sum()\n", ")\n", "\n", "# accounts > 5M€\n", "large_accounts = aum_last[aum_last >= 5_000_000].index\n", "\n", "print(\"Number of large accounts:\", len(large_accounts))\n", "\n", "# filter datasets\n", "stocks_large = stocks[\n", " stocks[\"Registrar Account - ID\"].isin(large_accounts)\n", "]\n", "\n", "stocks_repaired_large = stocks_repaired[\n", " stocks_repaired[\"Registrar Account - ID\"].isin(large_accounts)\n", "]\n", "\n", "flows_large = flows[\n", " flows[\"Registrar Account - ID\"].isin(large_accounts)\n", "]\n", "\n", "print(\"stocks_large rows:\", len(stocks_large))\n", "print(\"flows_large rows:\", len(flows_large))" ] }, { "cell_type": "code", "execution_count": 40, "id": "5bb68d76-33c2-4167-a392-53474ee60816", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "RAW SCORE : 0.00017577210434760302\n", "CLEAN SCORE : 0.0003594192235483644\n", "IMPROVEMENT : 0.0001836471192007614\n" ] } ], "source": [ "# ============================================================\n", "# FLOW CONSISTENCY SCORE\n", "# ============================================================\n", "\n", "def compute_flow_consistency(df):\n", "\n", " df = df.merge(\n", " flows_large,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " )\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"delta\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", " df[\"error\"] = (df[\"delta\"] - df[\"Quantity - NetFlows\"]).abs()\n", "\n", " score = 1 / (1 + df[\"error\"].mean())\n", "\n", " return score\n", "\n", "raw_score3 = compute_flow_consistency(stocks_large)\n", "clean_score3 = compute_flow_consistency(stocks_repaired_large)\n", "\n", "print(\"RAW SCORE :\", raw_score3)\n", "print(\"CLEAN SCORE :\", clean_score3)\n", "print(\"IMPROVEMENT :\", clean_score3 - raw_score3)" ] }, { "cell_type": "code", "execution_count": 43, "id": "64829ff4-46ee-4497-b6a2-87c835631062", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "RAW SCORE : 0.5623318126772077\n", "CLEAN SCORE : 0.6635861577517481\n", "IMPROVEMENT : 0.10125434507454045\n" ] } ], "source": [ "# PERSISTENT ACCOUNTING GAP SCORE\n", "\n", "def compute_persistent_gap_score(stocks_df, flows_df, persistence=3):\n", "\n", " df = stocks_df.merge(\n", " flows_df,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " ).copy()\n", "\n", " # previous AUM\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " # accounting gap\n", " df[\"gap\"] = (\n", " df[\"Quantity - AUM\"] -\n", " (df[\"prev_aum\"] + df[\"Quantity - NetFlows\"])\n", " )\n", "\n", " persistent_count = 0\n", " total_count = 0\n", "\n", " for (_, _), g in df.groupby([\"Registrar Account - ID\",\"Product - Isin\"]):\n", "\n", " gaps = g[\"gap\"].values\n", "\n", " for i in range(len(gaps) - persistence):\n", "\n", " window = gaps[i:i+persistence]\n", "\n", " if np.all(np.isfinite(window)):\n", "\n", " total_count += 1\n", "\n", " if np.all(np.abs(window - window[0]) < 1e-6):\n", "\n", " persistent_count += 1\n", "\n", " if total_count == 0:\n", " return np.nan\n", "\n", " score = 1 - persistent_count / total_count\n", "\n", " return score\n", "\n", "\n", "# ============================================================\n", "# COMPUTE SCORES\n", "# ============================================================\n", "\n", "raw_score = compute_persistent_gap_score(stocks_large, flows_large)\n", "clean_score = compute_persistent_gap_score(stocks_repaired_large, flows_large)\n", "\n", "print(\"RAW SCORE :\", raw_score)\n", "print(\"CLEAN SCORE :\", clean_score)\n", "print(\"IMPROVEMENT :\", clean_score - raw_score)" ] }, { "cell_type": "code", "execution_count": 44, "id": "3a1365ae-6376-4d5a-8213-2846cafb6fd6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "RAW SCORE : 0.09429052631578948\n", "CLEAN SCORE : 0.14027954256670902\n", "IMPROVEMENT : 0.04598901625091954\n", "RELATIVE IMPROVEMENT : 48.77374010714206 %\n" ] } ], "source": [ "# ============================================================\n", "# GAP HALF-LIFE SCORE\n", "# ============================================================\n", "\n", "import numpy as np\n", "\n", "def compute_gap_halflife_score(stocks_df, flows_df):\n", "\n", " df = stocks_df.merge(\n", " flows_df,\n", " on=[\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"],\n", " how=\"left\"\n", " )\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(\n", " [\"Registrar Account - ID\",\"Product - Isin\",\"Centralisation Date\"]\n", " ).copy()\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"gap\"] = (\n", " df[\"Quantity - AUM\"] -\n", " (df[\"prev_aum\"] + df[\"Quantity - NetFlows\"])\n", " )\n", "\n", " persistence_lengths = []\n", "\n", " for (_, _), g in df.groupby([\"Registrar Account - ID\",\"Product - Isin\"]):\n", "\n", " gaps = g[\"gap\"].values\n", "\n", " run_length = 1\n", "\n", " for i in range(1, len(gaps)):\n", "\n", " if np.isfinite(gaps[i]) and np.isfinite(gaps[i-1]) and abs(gaps[i] - gaps[i-1]) < 1e-6:\n", " run_length += 1\n", " else:\n", " if run_length > 1:\n", " persistence_lengths.append(run_length)\n", " run_length = 1\n", "\n", " if run_length > 1:\n", " persistence_lengths.append(run_length)\n", "\n", " if len(persistence_lengths) == 0:\n", " return np.nan\n", "\n", " mean_persistence = np.mean(persistence_lengths)\n", "\n", " score = 1 / (1 + mean_persistence)\n", "\n", " return score\n", "\n", "\n", "# ============================================================\n", "# COMPUTE SCORES\n", "# ============================================================\n", "\n", "raw_score = compute_gap_halflife_score(stocks_large, flows_large)\n", "clean_score = compute_gap_halflife_score(stocks_repaired_large, flows_large)\n", "\n", "print(\"RAW SCORE :\", raw_score)\n", "print(\"CLEAN SCORE :\", clean_score)\n", "print(\"IMPROVEMENT :\", clean_score - raw_score)\n", "print(\"RELATIVE IMPROVEMENT :\", (clean_score/raw_score - 1) * 100, \"%\")" ] }, { "cell_type": "code", "execution_count": 47, "id": "f431e22e-abd2-4d78-9684-4612dea12df7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['Value - AUM €']" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[\"Value - AUM €\"]" ] }, { "cell_type": "code", "execution_count": 53, "id": "fb3bc701-2f09-4876-8549-fd16b40f30e0", "metadata": {}, "outputs": [ { "data": { "image/png": "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AEASwQIAANiIYAEACCvTp09XamqqLMvS+++/f86ed+LEiRo7duxp2wwdOlS//e1v672WnTt3yrIsrVu3rt6f60wRLAAAtps4caIsy5JlWXI6nerSpYseeughVVRUnNV6N2/erBkzZuiFF17QgQMHNGrUqLOudfr06erbt2+t7f70pz9p3rx5Z/18Z6q6QNO2bVsdOHBA559/viQpJydHlmXp6NGj57y+H+KoEABAvRg5cqSys7NVVlamRYsWafLkyYqKitKUKVPOeF1er1eWZWnHjh2SpKuvvvqcT2iNj48/p893OhEREUpLSwt1GdWixwIAwokxUklJaG5neIIul8ultLQ0tW/fXr/+9a81YsQILViwQJJUVlame++9V61bt1ZsbKyysrKUk5Pjf+y8efOUkJCgBQsW6LzzzpPL5dIvfvELXXXVVZIqj144NVi8+OKL6tmzp5o1a6YePXro2WefDahl7969uvHGG5WUlKTY2Fj1799fn332mebNm6cZM2boq6++8vew1NQr8cOeg5KSEt18881yu91q1aqVHn/88SqPCXY7//73v6tnz55yu90aOXKkDhw4IKmyN+Xll1/WBx984K8vJycnYChk586dGjZsmCQpMTFRlmVp4sSJeuWVV9SiRQuVlZUF1DR27Fj9/Oc/P/2LdxbosQCAcHL8uOR2h+a5i4ul2Ng6Pzw6OlrfffedJOmOO+7Qpk2bNH/+fKWnp+u9997TyJEjtX79enXt2lWSdPz4cc2ePVsvvviiWrRooVatWmno0KG65ZZb/F+8kvT6669r6tSp+vOf/6zMzEytXbtWkyZNUmxsrCZMmKDi4mINGTJErVu31oIFC5SWlqYvv/xSPp9P48aN04YNG/TRRx/pH//4h6Tgeybuu+8+LVu2TB988IFatmypBx98UF9++WXAsEqw2/nYY4/p1VdflcPh0M9+9jPde++9ev3113Xvvfdq8+bNKioqUnZ2tiQpKSlJ+/fv9z9H27Zt9T//8z+67rrrtHXrVsXFxSk6OlpOp1O/+c1vtGDBAl1//fWSpMOHD+vDDz/U4sWL6/gq1o5gAQCoV8YYLVmyRH//+9915513avfu3crOztbu3buVnp4uSbr33nv10UcfKTs7W4888oikypODPfvss+rTp49/XQkJCZIUMAwwbdo0Pf7447r22mslSR07dtSmTZv0wgsvaMKECfrb3/6mvLw8ffHFF0pKSpIkdenSxf94t9utyMjIMxpaKC4u1ksvvaTXXntNw4cPlyS9/PLLatOmjb/NmWzn888/r86dO0uqDCMPPfSQv7bo6GiVlZXVWF9ERIR/u1q2bOnfR5J00003KTs72x8sXnvtNbVr105Dhw4NelvPFMECQNCMMco9UqKCEo8SY53qmBzLiZvOtZiYyp6DUD33GVi4cKHcbrfKy8vl8/l00003afr06crJyZHX61W3bt0C2peVlalFixb+n51OpzIyMk77HCUlJdqxY4duvfVWTZo0yb+8oqLC3/Owbt06ZWZm+r987bBjxw55PB5lZWX5lyUlJal79+7+n9evXx/UdsbExPhDhSS1atVKhw8ftqXOSZMm6cILL9S+ffvUunVrzZs3zz+xtr4QLAAELfdIiZZty5OnwidnZOUUrU4pIeqWb6os66yGI86lYcOG6bnnnpPT6VR6eroiIyu/coqLixUREaE1a9YoIiIi4DHuU4Z5oqOja/0CLP4+ZM2dOzfgS16Sf92hOhV6sNsZFRUVcJ9lWbZdcC4zM1N9+vTRK6+8ossvv1wbN27Uhx9+aMu6a0KwABC0ghKPPBU+dU5xa0desQpKPFJKqKtCQxUbGxsw5HBSZmamvF6vDh8+rMGDB5/Vc6Smpio9PV3ffvutxo8fX22bjIwMvfjii8rPz6+218LpdMrr9Z7R83bu3FlRUVH67LPP1K5dO0lSQUGBtm3bpiFDhkiybzuDqe/k1Uira3fbbbdpzpw52rdvn0aMGKG2bdvWuZZgcFQIgKAlxjrljHRoR16xnJEOJcbW/dLKaLq6deum8ePH6+abb9a7776r3Nxcff7555o5c2ad/pqeMWOGZs6cqaeeekrbtm3T+vXrlZ2drSeeeEKSdOONNyotLU1jx47VypUr9e233+p//ud/tGrVKklShw4dlJubq3Xr1unIkSNVjqKojtvt1q233qr77rtP//znP7VhwwZNnDgx4Fobdm1nhw4d9PXXX2vr1q06cuSIysvLq7Rp3769LMvSwoULlZeX5+/JkSrnWezdu1dz587VL37xi6Cft64IFgCC1jE5VkO6pah/+0QN6Zaijsnh0SWPhic7O1s333yz7rnnHnXv3l1jx47VF1984f/r/0zcdtttevHFF5Wdna3evXtryJAhmjdvnjp27Cip8q/5xYsXq2XLlho9erR69+6tWbNm+YcnrrvuOo0cOVLDhg1TSkqK3njjjaCe99FHH9XgwYN11VVXacSIEbr44ovVr18/27dz0qRJ6t69u/r376+UlBStXLmySpvWrVtrxowZeuCBB5Samqo77rjDf198fLyuu+46ud3uWs8cagfL2DWQEwaKiooUHx+vwsJCxcXFhbocAKjViRMnlJubq44dO9b5MtbA8OHD1atXLz311FOnbXe691uw36HMsQAAoJEqKChQTk6OcnJyqpw0rL4QLAAAaKQyMzNVUFCg2bNnBxwKW58IFgAANFI7d+4858/J5E0AAGAbggUAhIEmNM8eIWTH+4xgAQAN2MlDIj0eT4grQVNw/PhxSVXPBnommGMBAA1YZGSkYmJilJeXp6ioqIATMAF2Mcbo+PHjOnz4sBISEqqcgvxMECwAoAGzLEutWrVSbm6udu3aFepy0MglJCSc0VVeq0OwAIAGzul0qmvXrgyHoF5FRUWdVU/FSQQLAAgDDoeDM28iLDBYBwAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG4IFAACwDcECAADYJmyDxaxZs2RZln7729+GuhQAAPC9sAwWX3zxhV544QVlZGSEuhQAAHCKsAsWxcXFGj9+vObOnavExMRQlwMAAE4RdsFi8uTJuvLKKzVixIha25aVlamoqCjgBgAA6k9kqAs4E/Pnz9eXX36pL774Iqj2M2fO1IwZM+q5KgAAcFLY9Fjs2bNHd911l15//XU1a9YsqMdMmTJFhYWF/tuePXvquUoAAJo2yxhjQl1EMN5//31dc801ioiI8C/zer2yLEsOh0NlZWUB91WnqKhI8fHxKiwsVFxcXH2XDABAoxHsd2jYDIUMHz5c69evD1h2yy23qEePHrr//vtrDRUAAKD+hU2waN68uc4///yAZbGxsWrRokWV5QAAIDTCZo4FAABo+MKmx6I6OTk5oS4BAACcgh4LAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgm8hQFwAgfBhjlHukRAUlHiXGOtUxOVaWZYW6LAANCMECQNByj5Ro2bY8eSp8ckZWdnh2SnGHuCoADQlDIQCCVlDikafCp84pbnkqfCoo8YS6JAANDMECQNASY51yRjq0I69YzkiHEmOdoS4JQAPDUAiAoHVMjpWkgDkWAHAqggWAoFmWVTmnIiXUlQBoqBgKAQAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbhE2weO6555SRkaG4uDjFxcVp4MCB+r//+79QlwUAAE4RNsGiTZs2mjVrltasWaPVq1fr0ksv1dVXX62NGzeGujQAAPA9yxhjQl1EXSUlJenRRx/VrbfeGlT7oqIixcfHq7CwUHFxcfVcHQAAjUew36FheXVTr9ert99+WyUlJRo4cGCN7crKylRWVub/uaio6FyUBwBAkxU2QyGStH79erndbrlcLt1+++167733dN5559XYfubMmYqPj/ff2rZtew6rBQCg6QmroRCPx6Pdu3ersLBQ77zzjl588UUtW7asxnBRXY9F27ZtGQoBAOAMBTsUElbB4odGjBihzp0764UXXgiqPXMsAACom2C/Q8NqKOSHfD5fQI8EAAAIrbCZvDllyhSNGjVK7dq107Fjx/S3v/1NOTk5+vvf/x7q0gAAwPfCJlgcPnxYN998sw4cOKD4+HhlZGTo73//uy677LJQlwYAAL4XNsHipZdeCnUJAACgFmE9xwIAADQsBAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0ig224e/fuoNq1a9euzsUAAIDwFnSw6Nixo///xhhJkmVZAcssy5LX67WxPAAAEE6CDhaWZalNmzaaOHGirrrqKkVGBv1QAADQRASdDvbu3auXX35Z2dnZev755/Wzn/1Mt956q3r27Fmf9QEAgDAS9OTNtLQ03X///dqyZYveeecdFRQUKCsrSxdddJHmzp0rn89Xn3UCAIAwUKejQi6++GK99NJL2r59u2JiYnT77bfr6NGjNpcGAADCTZ2CxaeffqrbbrtN3bp1U3FxsZ555hklJCTYXBoAAAg3Qc+xOHDggF555RVlZ2eroKBA48eP18qVK3X++efXZ30AACCMBB0s2rVrp9atW2vChAkaM2aMoqKi5PP59PXXXwe0y8jIsL1IAAAQHixz8qQUtXA4/j1qcvL8FT98aH2ex2LmzJl69913tWXLFkVHR2vQoEGaPXu2unfvHvQ6ioqKFB8fr8LCQsXFxdVLnQAANEbBfocG3WORm5trS2F1tWzZMk2ePFkXXnihKioq9OCDD+ryyy/Xpk2bFBsbG9LaAABApaB7LBqavLw8tWzZUsuWLdMll1wS1GPosQAAoG5s77FYsGBBtcvj4+PVrVs3tWrV6syrPAuFhYWSpKSkpBrblJWVqayszP9zUVFRvdcFAEBTVqc5FlVWYln66U9/qrlz5yomJsa24mri8/k0ZswYHT16VJ988kmN7aZPn64ZM2ZUWU6PBQAAZybYHougz2Ph8/mqvRUUFOjjjz/Wl19+qT/84Q+2FF+byZMna8OGDZo/f/5p202ZMkWFhYX+2549e85JfQAANFV1OkHWqeLj43XppZfqySef1LvvvmtHTad1xx13aOHChVq6dKnatGlz2rYul0txcXEBNwAAUH9su0Rpjx49tHfvXrtWV4UxRnfeeafee+895eTkBFzGHQAANAy2BYtvv/1W6enpdq2uismTJ+tvf/ubPvjgAzVv3lwHDx6UVNljEh0dXW/PCwAAgnfWQyGStG7dOt1777268sor7VhdtZ577jkVFhZq6NChatWqlf/25ptv1ttzAgCAMxN0j0ViYqL/jJunKikpUUVFhS677LJqj8CwS5iebgMAgCYl6GAxZ86capfHxcWpe/fuOu+88+yqCQAAhKmgg8WECRNqbZOfn3/aE1YBAIDGzZY5FosXL9YNN9yg1q1b27E6AAAQpuocLHbt2qVp06apQ4cOuv766+VwOPTKK6/YWRsAAAgzZ3S4qcfj0bvvvqsXX3xRK1eu1IgRI7R3716tXbtWvXv3rq8aAQBAmAi6x+LOO+9Uenq6/vSnP+maa67R3r179b//+7+yLEsRERH1WSMAAAgTQfdYPPfcc7r//vv1wAMPqHnz5vVZEwAACFNB91i8+uqr+vzzz9WqVSuNGzdOCxculNfrrc/aAABAmAk6WNx44436+OOPtX79evXo0UOTJ09WWlqafD6fNm3aVJ81AgCAMGGZOp7S0hijxYsX66WXXtKCBQuUnJysa6+9Vk899ZTdNdom2GvJAwCAQMF+h9b5ImSWZemKK67QFVdcofz8fL3yyivKzs6u6+oAAEAjUOcei3BEjwUAAHUT7HeoLWfeBAAAkAgWAADARgQLAABgG1uDxYYNG+xcHQAACDNnHSyOHTumv/zlL8rKylLfvn1tKAkAAISrOgeL5cuXa8KECWrVqpV+//vfq02bNmpCB5gAAIBqnFGwOHjwoGbNmqWuXbtq9OjRqqio0FtvvaX9+/drxowZ9VUjAAAIE0GfIOuqq67SkiVLNGzYME2fPl1jx45VbGys/37LsuqlQAAAED6CDhYffvihbrrpJv32t79V//7967MmAAAQpoIeCvn0008VHR2tSy+9VN27d9dDDz2kHTt21GdtAAAgzAQdLC666CLNnTtXBw4c0P3336/FixerW7duuuiii/T000/r0KFD9VknAAAIA2d1rZCtW7fqpZde0quvvqpDhw7Jsix5vV4767MV1woBAKBuzsm1Qrp3764//vGP2rt3r959911deeWVZ7M6AAAQ5ri6KQAAqBVXNwUAAOccwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANmEVLJYvX66rrrpK6enpsixL77//fqhLAgAApwirYFFSUqI+ffromWeeCXUpAACgGpGhLuBMjBo1SqNGjQp1GQAAoAZhFSzOVFlZmcrKyvw/FxUVhbAaAAAav7AaCjlTM2fOVHx8vP/Wtm3bUJcEAECj1qiDxZQpU1RYWOi/7dmzJ9QlAQDQqDXqoRCXyyWXyxXqMgAAaDIadY8FAAA4t8Kqx6K4uFjffPON/+fc3FytW7dOSUlJateuXQgrAwAAUpgFi9WrV2vYsGH+n++++25J0oQJEzRv3rwQVQUAAE4Kq2AxdOhQGWNCXQYAAKgBcywAAIBtwqrHAkBoGWOUe6REBSUeJcY61TE5VpZlhbosAA0IwQJA0HKPlGjZtjx5KnxyRlZ2eHZKcYe4KgANCUMhAIJWUOKRp8KnzilueSp8KijxhLokAA0MwQJA0BJjnXJGOrQjr1jOSIcSY52hLglAA8NQCICgdUyOlaSAORYAcCqCBYCgWZZVOaciJdSVAGioGAoBAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALYhWAAAANsQLAAAgG0IFgAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA20SGugAA4cMYo9wjJSoo8Sgx1qmOybGyLCvUZQFoQAgWAIKWe6REy7blyVPhkzOyssOzU4o7xFUBaEgYCgEQtIISjzwVPnVOcctT4VNBiSfUJQFoYAgWAIKWGOuUM9KhHXnFckY6lBjrDHVJABoYhkIABK1jcqwkBcyxAIBTESwABM2yrMo5FSmhrgRAQ8VQCAAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA2xAsAACAbQgWAADANgQLAABgG06QBSBoXN0UQG0IFgCCxtVNAdSGoRAAQePqpgBqQ7AAELSEmCgdO1GuZdsO69iJciXERIW6JAANDMECAADYhmABIGhHj5erebMoDenWUs2bReno8fJQlwSggSFYAAhaYqxTzkiHduQVyxnpUGKsM9QlAWhgOCoEQNA6JsdKUsDhpgBwKnosAACAbeixABA0zmMBoDb0WAAIGuexAFAbggWAoDF5E0BtGAoBELT6nLzJdUiAxoFgAaBBYP4G0DgQLAAErT6//E+dv7Ejr7hy/kaKLasGcA4RLAAE7UhRqb7I/U5en08RDod6pbltCxbM3wAaB4IFgKB9vvOoVu8sUIXPp0iHQ+e1iteAzvZ0K3DyLaBxIFgACNrRUo+MpIQYp46dqNDRUvsON7Usq7L3g+EPIKwRLAAErcLrVeHxcuUXexThsFTh9Ya6JAANDOexABC0gpIyGRk5HJKRUUFJWahLAtDAECwABO2ExydJcliBPwPASQyFAAhabLMIeX1SuSr/KoltFhHqkgA0MPRYAAjKyTNjnuyj8Ek6VHQilCUBaIAIFgCC8m1esb7NOx6w7FAhFyEDEIihEABB2bS/SGUVgXMqIhwmRNU0LFznBPg3ggWAoBhV18VJp6fEdU6AU/FbAUBQeqXHKeIHvzE8npqHQowx+javWGt25uvbvGIZ03h7N069zomnwld5nROgiQq7YPHMM8+oQ4cOatasmbKysvT555+HuiSgUTo1GOw4fEzGGB0rD2yTW1ihL749omVbD2t17nf+AGGM0fJteXpt1S4t3nRQy7bmKfdISWg25Hv1GXS4zgnwb2E1FPLmm2/q7rvv1vPPP6+srCzNmTNHV1xxhbZu3aqWLVuGujwgLJ06PyAhJkqSlF9cpn99m68vduUr0uGQ0/LpX99+V+3jx//lM0U3c6hZVITimkXpyt5pOlBYpjW7CxThcKhPm3jtyS/VgcLj+lGXFA3umiyHw1HluYOdm3CmjznZfsO+Qm07dExuV6RcUZWHydo1XFHddU7qNO/CmMqbzxf4b3XLTndfQ1oW6udvyjVdeqn0xBO2vMfPhGXCqH8yKytLF154of785z9Lknw+n9q2bas777xTDzzwQK2PLyoqUnx8vAoLCxUXF1ff5aI+nfwQNYRfCLX8kjBerw4cPa5jxz1q7opUqziXrHNQu/F6deRYmUpPeBQTFaEWMZGyTu47n08+n0+5h4q049AxHTtRrsToSEVakuXzKb+4TLuOlMjn9Ukyssz3N0kO45MlI8f3z3NymeP7NpEy/vsjLKlZhCWHjGKcEYqUUVJ0lBKiI5UQHSn5jPZ8V6yKCp+cDqlNQjM1d0acdrtKTniUX1wm4/XJ6/Up1hkhd5RD0VEOWdXskxNl5SoqLdcJT4UqKryKc0XKeL2KcUYoNspRb7/0vV6fKrxeWT6fLCNFWJX7qcbHhs+vYoSJgstHq+C1N22bTBzsd2jY9Fh4PB6tWbNGU6ZM8S9zOBwaMWKEVq1aVe1jysrKVFb271MOFxUV2VqTyc9XwaNzVFpWoehIS4nRkXX/wgj1F2G41RRGLEnpIXre013PyyGp8/e3UOp1hu1jv78Fq9n3t3Mt4vvbOWVZlTeHI/DfU5efsswrS+VGMt//HBUZocjIiKqPre7/p1t2ujZnuo4aaq92WV2f14512F37WazjQFGZvt5fpKK4JBVvy5N0bicTh02wOHLkiLxer1JTUwOWp6amasuWLdU+ZubMmZoxY0a91bTn2/1qN+u/6239qGfn6JfEiQqfSr1SVKRDHp9RtDNS0a6o+vsl+f2/BaXlOlRcrnKf0YkKn2JcUWqf4pY72ilZlnK/K9HOghPy+qTjFT5FRjrUwt1MxuHQ/sITKvUaGVnyWQ4ZS5X9EJb1/bJ//998/3/f9z//uxZLkZER6pDcXGVen46V+1Ti8SohxqnYaKeiIiMU7YrSgWNlio6K1HGvT73aJOj8Nomn3b684jJtPVSswyXlOlparu7pCSqr8KljilsdU+OqPPZg0QltPHBM5T6j0gqj9KRYtUuOVVp8tKyT+/xM93sQr92eguP6fNdReXyVr/2ATslql+wObH+a5123t1Br9xSqQ4pbud+VKLN9kjI7Jp/+ec/Qup35Wr2rQJ1T3NqRV6z+7RPVr0NSPXzYcC7t35mvnd+/rvl5xZWTic/hVYPDJljUxZQpU3T33Xf7fy4qKlLbtm1tW3++w6WSH49T82inCsu8SolzqWVcdN2+nM7mC+5cpe9QJ/hgn7e2NnX8JVxX+/OKAw5FHNIt5Zz89VCQV6y3/rVLuUdKlBrXTPHRkbr8vDT/F8furYf15yXbteu7Enl9Ri2bu3TLxR2VFt9Mby/boS92FugHp604reZRUkKsS7GuSDkjHHI4LA3vkapLh3TUp98W6NvdBdqRV6ySsgo5HA71SGuu/h0StXtngQpLyxUfHaX+ma2lls1P+zzJxujYkRLl7ytU/qFjWv/9nIkO3VKkavZrqjE6HoJzTLQxRuVd//28bZNjz+h9F+d1SsekbaU+OePiFJ+SKEVH21ojk04bp1C/rmETLJKTkxUREaFDhw4FLD906JDS0tKqfYzL5ZLL5aq3muLattKyB2YGfGG05Nh1/EB1E/vO1fMO6ZYiGSkq0lJaXHTAL5jBXZO17dAxLdt6WK0SYpQQHakuKW7165CkNgnRmrt8h1ZuP6wDRRU63cXR3VHS4K4puvvy7iosrVCJx6tYZ4SS3C7/l/iQ7i11SbcUfZtXrE37i2RUefhqx+RYtUuKPaN9Y1mWf3KkJFmSzvt+XTW175TiPqd/sdnxvOfifROq9ybqV6hf17AJFk6nU/369dOSJUs0duxYSZWTN5csWaI77rgjJDWF+sVDeAjlF9sl3VLUNimm2veow+HQ8J6pioxw+MNxktsly7LUJTVOs37SV7lHSrRxX6E+2nBAq3ce1aHiqpdJn3hxZ12T2Vqda+lpsCxLnVs2r9KuLvsm90iJlm8/4q/bsqxz0gtxLp2L902o3puoX6F+XcMmWEjS3XffrQkTJqh///4aMGCA5syZo5KSEt1yyy0hqSfULx5Qm9reo6cLxycf2zE5Vj3T45Sz5bAeXrRFp06djZZ07QVtznmoPvWEVDtCMIYMoGZhFSzGjRunvLw8TZ06VQcPHlTfvn310UcfVZnQCSBQTedUCCYcW5alLi2bq0vL5npi8RYdrzjlvsjQnLo61GPIAGoWVsFCku64446QDX0A4cqua1mUV5z+53OFYUig4Qq7YAHgzNk1dNAsylJ5uQn4ORQYhgQaLkeoCwBQ/+wYOjDGKLNdfMCywV1b2FUigEaCHgugCbBj6CD3SInatHAr9XCpTpR7ldLcpesHtLe7VABhjmABNAF2DB18V1ymCp/R+enNVVharivOT9OQblz8D0AgggWAoOw4XKzVuQU6VlauyAiHTnh8Z33uiDpdARRAg0awABCUg0WlKi2vPAdnUWm5lm87rNEZrWo9Mdbp2HW0CoCGg8mbAILidkXJZ4yOl3llfEaHjpVpw77Cs1rnqUereCp8lUerAAhrBAsAQRnao6VaJ0TLJykiwqEKn1Hesaqn+D4TnOgKaHwYCgEQlM4pbg3olKT9hScUGWEp0rJkAk7wfeY40RXQ+BAsAATFsiydlxanr1MK5YyMkKfCq9Tmzc56nZzoCmhcGAoBELSTlyd3RVReuvy89LhQlwSggaHHAkDQLMtSYoxTDstSfHQUh4YCqIIeCwBBKyjxyOM1ap0QLY/XcBQHgCrosQAQtBKPV7u/K9H2Qz65Ih0q8XhDXRKABoZgASBosc4ItW8Ro8QYpwqOexTrjAh1SQAaGIZCAAQtye1SWny0LMtSWny0ktyuUJcEoIGhxwJA0DjvBIDaECwABI3zTgCoDcECQNB8Pp9WbD+ivQXH1SYxRoO7JsvhYEQVwL8RLAAEbcX2I3rj890qq6g8KkSShnRvGeKqADQk/KkBIGh7C46rrMKnXunxKqvwaW/B8VCXBKCBIVgACFqbxBi5Ih3auL9QrkiH2iTGhLokAA0MQyEAgja4a7IkBcyxAIBTESwABM3hcDCnAsBpESwA1JkxRrlHSgLOa8GFyYCmjWABoM5yj5Ro2bY8eSp8cn5/lEinFHeIqwIQSkzeBFBnBSUeeSp86pzilqfCx9VOARAsANRdYqxTzkiHduQVyxnpUGKsM9QlAQgxhkIA1BnXDgHwQwQLAHXGtUMA/BBDIQAAwDYECwAAYBuCBQAAsA3BAgAA2IZgAQAAbEOwAAAAtiFYAAAA23AeCwBB46JjAGpDsADqqCl+yXLRMQC1IVgAddQUv2RPvejYjrziyouOcdZNAKdgjgVQR03xyp5cdAxAbeixAOqoKX7JctExALUhWAB11BS/ZLnoGIDaECyAOuJLFgCqYo4FAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsAAAALbhImQAzpgxRrlHSgKu7GpZVqjLAtAAECwAnLHcIyVati1PngqfnJGVHZ+dUtwhrgpAQ8BQCIAzVlDikafCp84pbnkqfCoo8YS6JAANBMECwBlLjHXKGenQjrxiOSMdSox1hrokAA0EQyEAzljH5FhJCphjAQASwQJAHViWVTmnIiXUlQBoaBgKAQAAtiFYAAAA2xAsAACAbcImWDz88MMaNGiQYmJilJCQEOpyAABANcImWHg8Hl1//fX69a9/HepSAABADcLmqJAZM2ZIkubNmxfaQgAAQI3CJljURVlZmcrKyvw/FxUVhbAaAAAav7AZCqmLmTNnKj4+3n9r27ZtqEsCAKBRC2mweOCBB2RZ1mlvW7ZsqfP6p0yZosLCQv9tz549NlYPAAB+KKRDIffcc48mTpx42jadOnWq8/pdLpdcLledHw8AAM5MSINFSkqKUlI4JzAAAI1F2Eze3L17t/Lz87V79255vV6tW7dOktSlSxe53e7QFgcAACSFUbCYOnWqXn75Zf/PmZmZkqSlS5dq6NChIaoKAACcyjLGmFAXca4UFRUpPj5ehYWFiouLC3U5AACEjWC/Qxv14aYAAODcIlgAAADbhM0cCzucHPXhDJwAAJyZk9+dtc2gaFLB4tixY5LEGTgBAKijY8eOKT4+vsb7m9TkTZ/Pp/3798sYo3bt2mnPnj1M4vxeUVGR2rZtyz45BfukeuyXqtgnVbFPqgr3fWKM0bFjx5Seni6Ho+aZFE2qx8LhcKhNmzb+7py4uLiwfHHrE/ukKvZJ9dgvVbFPqmKfVBXO++R0PRUnMXkTAADYhmABAABs0ySDhcvl0rRp07hA2SnYJ1WxT6rHfqmKfVIV+6SqprJPmtTkTQAAUL+aZI8FAACoHwQLAABgG4IFAACwDcECAADYJqyDxcMPP6xBgwYpJiZGCQkJ1baxLKvKbf78+QFtcnJydMEFF8jlcqlLly6aN29elfU888wz6tChg5o1a6asrCx9/vnnAfefOHFCkydPVosWLeR2u3Xdddfp0KFDdm1q0ILZJ7t379aVV16pmJgYtWzZUvfdd58qKioC2jSmffJDHTp0qPKemDVrVkCbr7/+WoMHD1azZs3Utm1b/fGPf6yynrfffls9evRQs2bN1Lt3by1atCjgfmOMpk6dqlatWik6OlojRozQ9u3b63Xb6lttr3m4mj59epX3RI8ePfz3B/NetutzFUrLly/XVVddpfT0dFmWpffffz/g/mDe0/n5+Ro/frzi4uKUkJCgW2+9VcXFxQFt7Ph8nSu17ZOJEydWee+MHDkyoE1j2ye1MmFs6tSp5oknnjB33323iY+Pr7aNJJOdnW0OHDjgv5WWlvrv//bbb01MTIy5++67zaZNm8zTTz9tIiIizEcffeRvM3/+fON0Os1f//pXs3HjRjNp0iSTkJBgDh065G9z++23m7Zt25olS5aY1atXm4suusgMGjSo3ra9JrXtk4qKCnP++eebESNGmLVr15pFixaZ5ORkM2XKFH+bxrZPfqh9+/bmoYceCnhPFBcX++8vLCw0qampZvz48WbDhg3mjTfeMNHR0eaFF17wt1m5cqWJiIgwf/zjH82mTZvM73//exMVFWXWr1/vbzNr1iwTHx9v3n//ffPVV1+ZMWPGmI4dOwa8/8JJMK95uJo2bZrp1atXwHsiLy/Pf39t72W7PlehtmjRIvP//t//M++++66RZN57772A+4N5T48cOdL06dPH/Otf/zIrVqwwXbp0MTfeeKP/frs+X+dKbftkwoQJZuTIkQHvnfz8/IA2jW2f1Casg8VJ2dnZpw0WP3wjnOo///M/Ta9evQKWjRs3zlxxxRX+nwcMGGAmT57s/9nr9Zr09HQzc+ZMY4wxR48eNVFRUebtt9/2t9m8ebORZFatWlWHLTp7Ne2TRYsWGYfDYQ4ePOhf9txzz5m4uDhTVlZmjGm8++Sk9u3bmyeffLLG+5999lmTmJjo3x/GGHP//feb7t27+3++4YYbzJVXXhnwuKysLPOrX/3KGGOMz+czaWlp5tFHH/Xff/ToUeNyucwbb7xh05acW7W95uFs2rRppk+fPtXeF8x72a7PVUPyw9+dwbynN23aZCSZL774wt/m//7v/4xlWWbfvn3GGHs+X6FSU7C4+uqra3xMY98n1QnroZBgTZ48WcnJyRowYID++te/BlzyddWqVRoxYkRA+yuuuEKrVq2SJHk8Hq1ZsyagjcPh0IgRI/xt1qxZo/Ly8oA2PXr0ULt27fxtGopVq1apd+/eSk1N9S+74oorVFRUpI0bN/rbNPZ9MmvWLLVo0UKZmZl69NFHA7qsV61apUsuuUROp9O/7IorrtDWrVtVUFDgb3O6fZSbm6uDBw8GtImPj1dWVlaD2P4zFcxrHu62b9+u9PR0derUSePHj9fu3bslBfdetuNz1dAF855etWqVEhIS1L9/f3+bESNGyOFw6LPPPvO3OdvPV0OTk5Ojli1bqnv37vr1r3+t7777zn9fU9wnjf4iZA899JAuvfRSxcTEaPHixfqP//gPFRcX6ze/+Y0k6eDBgwG/DCQpNTVVRUVFKi0tVUFBgbxeb7VttmzZ4l+H0+msMqchNTVVBw8erL+Nq4Oatvfkfadr01j2yW9+8xtdcMEFSkpK0qeffqopU6bowIEDeuKJJyRV1t6xY8eAx5y6jxITE2vcR6fuw1MfV12bcHLkyJFaX/NwlpWVpXnz5ql79+46cOCAZsyYocGDB2vDhg1BvZft+FxFR0fX09bZI5j39MGDB9WyZcuA+yMjI5WUlBTQ5mw/Xw3JyJEjde2116pjx47asWOHHnzwQY0aNUqrVq1SREREk9wnDS5YPPDAA5o9e/Zp22zevDlgYtXp/Nd//Zf//5mZmSopKdGjjz7qDxbhwO590hidyT66++67/csyMjLkdDr1q1/9SjNnzmz0p9pF9UaNGuX/f0ZGhrKystS+fXu99dZbDf4LH6H105/+1P//3r17KyMjQ507d1ZOTo6GDx8ewspCp8EFi3vuuUcTJ048bZtOnTrVef1ZWVn67//+b5WVlcnlciktLa3K7O5Dhw4pLi5O0dHRioiIUERERLVt0tLSJElpaWnyeDw6evRowF81p7Y5G3buk7S0tCoz+U9u26nb09D3yQ+dzT7KyspSRUWFdu7cqe7du9e4/VLt++jU+08ua9WqVUCbvn37Br1dDUVycnKtr3ljkpCQoG7duumbb77RZZddVut72Y7PVUMXzHs6LS1Nhw8fDnhcRUWF8vPza90Ppz5HbZ+vhqxTp05KTk7WN998o+HDhzfJfdLg5likpKSoR48ep72dOg51ptatW6fExET/X6YDBw7UkiVLAtp8/PHHGjhwoCTJ6XSqX79+AW18Pp+WLFnib9OvXz9FRUUFtNm6dat2797tb3M27NwnAwcO1Pr16wPe6B9//LHi4uJ03nnn+ds09H3yQ2ezj9atWyeHw+Hvrhw4cKCWL1+u8vLygO3v3r27EhMT/W1Ot486duyotLS0gDZFRUX67LPP6mX761swr3ljUlxcrB07dqhVq1ZBvZft+Fw1dMG8pwcOHKijR49qzZo1/jb//Oc/5fP5lJWV5W9ztp+vhmzv3r367rvv/OGrSe6TUM8ePRu7du0ya9euNTNmzDBut9usXbvWrF271hw7dswYY8yCBQvM3Llzzfr168327dvNs88+a2JiYszUqVP96zh5CNh9991nNm/ebJ555plqD610uVxm3rx5ZtOmTeaXv/ylSUhICJgBfvvtt5t27dqZf/7zn2b16tVm4MCBZuDAgeduZ3yvtn1y8rC4yy+/3Kxbt8589NFHJiUlpdrD4hrLPjnVp59+ap588kmzbt06s2PHDvPaa6+ZlJQUc/PNN/vbHD161KSmppqf//znZsOGDWb+/PkmJiamyqFfkZGR5rHHHjObN28206ZNq/Zw04SEBPPBBx+Yr7/+2lx99dVhf7hpba95uLrnnntMTk6Oyc3NNStXrjQjRowwycnJ5vDhw8aY2t/Ldn2uQu3YsWP+3xmSzBNPPGHWrl1rdu3aZYwJ7j09cuRIk5mZaT777DPzySefmK5duwYcWmnX5+tcOd0+OXbsmLn33nvNqlWrTG5urvnHP/5hLrjgAtO1a1dz4sQJ/zoa2z6pTVgHiwkTJhhJVW5Lly41xlQe0tO3b1/jdrtNbGys6dOnj3n++eeN1+sNWM/SpUtN3759jdPpNJ06dTLZ2dlVnuvpp5827dq1M06n0wwYMMD861//Cri/tLTU/Md//IdJTEw0MTEx5pprrjEHDhyor02vUW37xBhjdu7caUaNGmWio6NNcnKyueeee0x5eXnAehrTPjnVmjVrTFZWlomPjzfNmjUzPXv2NI888kjALwFjjPnqq6/MxRdfbFwul2ndurWZNWtWlXW99dZbplu3bsbpdJpevXqZDz/8MOB+n89n/uu//sukpqYal8tlhg8fbrZu3Vqv21ffanvNw9W4ceNMq1atjNPpNK1btzbjxo0z33zzjf/+YN7Ldn2uQmnp0qXV/v6YMGGCMSa49/R3331nbrzxRuN2u01cXJy55ZZb/H/YnGTH5+tcOd0+OX78uLn88stNSkqKiYqKMu3btzeTJk2qErYb2z6pDZdNBwAAtmlwcywAAED4IlgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQIAANiGYAEAAGxDsABQraFDh+q3v/3tOX1Oj8ejLl266NNPPz2nz/vDGjp06KDVq1eHrAYgnBEsgCZq4sSJsiyryu2bb74JWU3PP/+8OnbsqEGDBkmSdu7cKcuytG7dulofu3z5cl111VVKT0+XZVl6//33a33M9OnTq1xt1ul06t5779X9999fhy0AQLAAmrCRI0fqwIEDAbeOHTuGpBZjjP785z/r1ltvrdPjS0pK1KdPHz3zzDNnXcv48eP1ySefaOPGjWe9LqCpIVgATZjL5VJaWlrALSIiotq2BQUFuvnmm5WYmKiYmBiNGjVK27dvl1QZClJSUvTOO+/42/ft29d/6WhJ+uSTT+RyuXT8+PFq179mzRrt2LFDV155pX/ZyZCTmZkpy7I0dOjQGrdl1KhR+sMf/qBrrrkmqG2fN2+eZsyYoa+++srfWzNv3jxJUmJion70ox9p/vz5Qa0LwL8RLAAEZeLEiVq9erUWLFigVatWyRij0aNHq7y8XJZl6ZJLLlFOTo6kyhCyefNmlZaWasuWLZKkZcuW6cILL1RMTEy161+xYoW6deum5s2b+5d9/vnnkqR//OMfOnDggN59913btmfcuHG655571KtXL39vzbhx4/z3DxgwQCtWrLDt+YCmgmABNGELFy6U2+32366//vpq223fvl0LFizQiy++qMGDB6tPnz56/fXXtW/fPv9chqFDh/qDxfLly5WZmRmwLCcnR0OGDKmxll27dik9PT1gWUpKiiSpRYsWSktLU1JS0tlt8Cmio6PldrsVGRnp762Jjo7235+enq5du3bZ9nxAU0GwAJqwYcOGad26df7bU089VW27zZs3KzIyUllZWf5lLVq0UPfu3bV582ZJ0pAhQ7Rp0ybl5eVp2bJlGjp0qD9YlJeX69NPPz3tUEZpaamaNWtWa80rVqwICEOvv/76mW10kKKjo2sctgFQs8hQFwAgdGJjY9WlSxdb1tW7d28lJSVp2bJlWrZsmR5++GGlpaVp9uzZ+uKLL1ReXu4/2qM6ycnJWr9+fa3P079//4CjRFJTU+0ov4r8/Hx/jwmA4NFjAaBWPXv2VEVFhT777DP/su+++05bt27VeeedJ0myLEuDBw/WBx98oI0bN+riiy9WRkaGysrK9MILL6h///6KjY2t8TkyMzO1ZcsWGWP8y5xOpyTJ6/X6l0VHR6tLly7+26lzMs6U0+kMWPepNmzYoMzMzDqvG2iqCBYAatW1a1ddffXVmjRpkj755BN99dVX+tnPfqbWrVvr6quv9rcbOnSo3njjDfXt21dut1sOh0OXXHKJXn/99dPOr5Aqh2WKi4sDDvFs2bKloqOj9dFHH+nQoUMqLCys8fHFxcX+IR1Jys3N1bp167R79+4aH9OhQwd/uyNHjqisrMx/34oVK3T55ZfXtmsA/ADBAkBQsrOz1a9fP/34xz/WwIEDZYzRokWLFBUV5W8zZMgQeb3egLkUQ4cOrbKsOi1atNA111wTMGciMjJSTz31lF544QWlp6cHhJgfWr16tTIzM/29DHfffbcyMzM1depUf5vp06erQ4cO/p+vu+46jRw5UsOGDVNKSoreeOMNSdKqVatUWFion/zkJ8HsGgCnsMyp/Y4AEEJff/21LrvsMu3YsUNut9v29U+YMCHgfBU1GTdunPr06aMHH3zQ9hqAxo7JmwAajIyMDM2ePVu5ubnq3bu3res2xignJ0effPLJadt5PB717t1bv/vd72x9fqCpoMcCAADYhjkWAADANgQLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2/x+u5aLDWc87uQAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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mzbm4Ycf9tknioqKvK8XFBSY5s2bm9GjR5u1a9eat99+20RERJiXXnrJ22b58uUmJCTE/PWvfzXr1683//Vf/2XCwsLMmjVrvG2mT59uYmNjzYcffmi+++47c+WVV5q0tDSf/S+Y+POZB6vJkyebbt26+ewTeXl53ter25ft+rkKtPnz55s//elP5v333zeSzAcffODzuj/79LBhw0yvXr3Mv//9b7Ns2TLToUMHc9NNN3lft+vn62ypbpuMHTvWDBs2zGffOXTokE+bhrZNqhPUweKkzMzM0waLn+4IFf3xj3803bp185k2atQoc9lll3mfn3feeWbChAne526326Smpppp06YZY4w5fPiwCQsLM3PmzPG22bBhg5FkVqxYUYs1OnOn2ibz5883DofD7Nu3zzvthRdeMDExMaakpMQY03C3yUlt2rQxTzzxxClff/755018fLx3exhjzP333286d+7sfX7DDTeYESNG+LwvIyPD3HbbbcYYYzwej0lOTjaPPfaY9/XDhw8bl8tl3n77bZvW5Oyq7jMPZpMnTza9evWq8jV/9mW7fq7qk5/+7vRnn16/fr2RZL7++mtvm//7v/8zlmWZ3bt3G2Ps+fkKlFMFi6uuuuqU72no26QqQX0oxF8TJkxQYmKizjvvPP3P//yPzy1fV6xYoaFDh/q0v+yyy7RixQpJUmlpqVatWuXTxuFwaOjQod42q1atUllZmU+bLl26qHXr1t429cWKFSvUo0cPNW/e3DvtsssuU2FhodatW+dt09C3yfTp09W0aVOlp6frscce8+myXrFihS666CI5nU7vtMsuu0ybNm1Sfn6+t83ptlFOTo727dvn0yY2NlYZGRn1Yv1ryp/PPNht2bJFqampateunUaPHq3c3FxJ/u3Ldvxc1Xf+7NMrVqxQXFyc+vbt620zdOhQORwOffnll942Z/rzVd9kZWWpWbNm6ty5s37729/qhx9+8L7WGLdJg78J2cMPP6yLL75YkZGRWrhwoX73u9+pqKhId911lyRp3759Pr8MJKl58+YqLCzUsWPHlJ+fL7fbXWWbjRs3eufhdDorjWlo3ry59u3bV3crVwunWt+Tr52uTUPZJnfddZd+9rOfKSEhQV988YUmTZqkvXv3aubMmZJO1J6WlubznorbKD4+/pTbqOI2rPi+qtoEk4MHD1b7mQezjIwMzZo1S507d9bevXs1depUDRgwQGvXrvVrX7bj5yoiIqKO1s4e/uzT+/btU7NmzXxeDw0NVUJCgk+bM/35qk+GDRuma6+9VmlpacrOztaDDz6o4cOHa8WKFQoJCWmU26TeBYsHHnhAM2bMOG2bDRs2+AysOp3//u//9v4/PT1dxcXFeuyxx7zBIhjYvU0aoppso3vuucc7rWfPnnI6nbrttts0bdq0Bn+pXVRt+PDh3v/37NlTGRkZatOmjd599916/4WPwLrxxhu9/+/Ro4d69uyp9u3bKysrS0OGDAlgZYFT74LFvffeq3Hjxp22Tbt27Wo9/4yMDP35z39WSUmJXC6XkpOTK43u3r9/v2JiYhQREaGQkBCFhIRU2SY5OVmSlJycrNLSUh0+fNjnr5qKbc6EndskOTm50kj+k+tWcX3q+zb5qTPZRhkZGSovL9f27dvVuXPnU66/VP02qvj6yWkpKSk+bXr37u33etUXiYmJ1X7mDUlcXJw6deqkrVu36pJLLql2X7bj56q+82efTk5O1oEDB3zeV15erkOHDlW7HSouo7qfr/qsXbt2SkxM1NatWzVkyJBGuU3q3RiLpKQkdenS5bSPisehamr16tWKj4/3/mXar18/LVq0yKfNp59+qn79+kmSnE6n+vTp49PG4/Fo0aJF3jZ9+vRRWFiYT5tNmzYpNzfX2+ZM2LlN+vXrpzVr1vjs6J9++qliYmJ0zjnneNvU923yU2eyjVavXi2Hw+HtruzXr5+WLl2qsrIyn/Xv3Lmz4uPjvW1Ot43S0tKUnJzs06awsFBffvllnax/XfPnM29IioqKlJ2drZSUFL/2ZTt+ruo7f/bpfv366fDhw1q1apW3zWeffSaPx6OMjAxvmzP9+arPdu3apR9++MEbvhrlNgn06NEzsWPHDvPtt9+aqVOnmujoaPPtt9+ab7/91hw5csQYY8zcuXPNyy+/bNasWWO2bNlinn/+eRMZGWkeeugh7zxOngJ23333mQ0bNpjnnnuuylMrXS6XmTVrllm/fr35zW9+Y+Li4nxGgN9+++2mdevW5rPPPjMrV640/fr1M/369Tt7G+NH1W2Tk6fFXXrppWb16tVmwYIFJikpqcrT4hrKNqnoiy++ME888YRZvXq1yc7ONv/4xz9MUlKSGTNmjLfN4cOHTfPmzc2vfvUrs3btWjN79mwTGRlZ6dSv0NBQ8/jjj5sNGzaYyZMnV3m6aVxcnPnoo4/M999/b6666qqgP920us88WN17770mKyvL5OTkmOXLl5uhQ4eaxMREc+DAAWNM9fuyXT9XgXbkyBHv7wxJZubMmebbb781O3bsMMb4t08PGzbMpKenmy+//NJ8/vnnpmPHjj6nVtr183W2nG6bHDlyxEycONGsWLHC5OTkmH/961/mZz/7menYsaM5fvy4dx4NbZtUJ6iDxdixY42kSo/FixcbY06c0tO7d28THR1toqKiTK9evcyLL75o3G63z3wWL15sevfubZxOp2nXrp3JzMystKxnnnnGtG7d2jidTnPeeeeZf//73z6vHzt2zPzud78z8fHxJjIy0lxzzTVm7969dbXqp1TdNjHGmO3bt5vhw4ebiIgIk5iYaO69915TVlbmM5+GtE0qWrVqlcnIyDCxsbEmPDzcdO3a1Tz66KM+vwSMMea7774zF154oXG5XKZFixZm+vTpleb17rvvmk6dOhmn02m6detmPv74Y5/XPR6P+e///m/TvHlz43K5zJAhQ8ymTZvqdP3qWnWfebAaNWqUSUlJMU6n07Ro0cKMGjXKbN261fu6P/uyXT9XgbR48eIqf3+MHTvWGOPfPv3DDz+Ym266yURHR5uYmBhz8803e/+wOcmOn6+z5XTb5OjRo+bSSy81SUlJJiwszLRp08aMHz++UthuaNukOtw2HQAA2KbejbEAAADBi2ABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGAbggUAALANwQJAlQYNGqS77777rC6ztLRUHTp00BdffHFWl/vTGtq2bauVK1cGrAYgmBEsgEZq3Lhxsiyr0mPr1q0Bq+nFF19UWlqa+vfvL0navn27LMvS6tWrq33v0qVLNXLkSKWmpsqyLH344YfVvmfKlCmV7jbrdDo1ceJE3X///bVYAwAEC6ARGzZsmPbu3evzSEtLC0gtxhg9++yzuuWWW2r1/uLiYvXq1UvPPffcGdcyevRoff7551q3bt0ZzwtobAgWQCPmcrmUnJzs8wgJCamybX5+vsaMGaP4+HhFRkZq+PDh2rJli6QToSApKUnvvfeet33v3r29t46WpM8//1wul0tHjx6tcv6rVq1Sdna2RowY4Z12MuSkp6fLsiwNGjTolOsyfPhw/eUvf9E111zj17rPmjVLU6dO1XfffeftrZk1a5YkKT4+XhdccIFmz57t17wA/AfBAoBfxo0bp5UrV2ru3LlasWKFjDG6/PLLVVZWJsuydNFFFykrK0vSiRCyYcMGHTt2TBs3bpQkLVmyROeee64iIyOrnP+yZcvUqVMnNWnSxDvtq6++kiT961//0t69e/X+++/btj6jRo3Svffeq27dunl7a0aNGuV9/bzzztOyZctsWx7QWBAsgEZs3rx5io6O9j6uv/76Kttt2bJFc+fO1SuvvKIBAwaoV69eevPNN7V7927vWIZBgwZ5g8XSpUuVnp7uMy0rK0sDBw48ZS07duxQamqqz7SkpCRJUtOmTZWcnKyEhIQzW+EKIiIiFB0drdDQUG9vTUREhPf11NRU7dixw7blAY0FwQJoxAYPHqzVq1d7H08//XSV7TZs2KDQ0FBlZGR4pzVt2lSdO3fWhg0bJEkDBw7U+vXrlZeXpyVLlmjQoEHeYFFWVqYvvvjitIcyjh07pvDw8GprXrZsmU8YevPNN2u20n6KiIg45WEbAKcWGugCAAROVFSUOnToYMu8evTooYSEBC1ZskRLlizRI488ouTkZM2YMUNff/21ysrKvGd7VCUxMVFr1qypdjl9+/b1OUukefPmdpRfyaFDh7w9JgD8R48FgGp17dpV5eXl+vLLL73TfvjhB23atEnnnHOOJMmyLA0YMEAfffSR1q1bpwsvvFA9e/ZUSUmJXnrpJfXt21dRUVGnXEZ6ero2btwoY4x3mtPplCS53W7vtIiICHXo0MH7qDgmo6acTqfPvCtau3at0tPTaz1voLEiWACoVseOHXXVVVdp/Pjx+vzzz/Xdd9/pl7/8pVq0aKGrrrrK227QoEF6++231bt3b0VHR8vhcOiiiy7Sm2++edrxFdKJwzJFRUU+p3g2a9ZMERERWrBggfbv36+CgoJTvr+oqMh7SEeScnJytHr1auXm5p7yPW3btvW2O3jwoEpKSryvLVu2TJdeeml1mwbATxAsAPglMzNTffr00RVXXKF+/frJGKP58+crLCzM22bgwIFyu90+YykGDRpUaVpVmjZtqmuuucZnzERoaKiefvppvfTSS0pNTfUJMT+1cuVKpaene3sZ7rnnHqWnp+uhhx7ytpkyZYratm3rfX7ddddp2LBhGjx4sJKSkvT2229LklasWKGCggL9/Oc/92fTAKjAMhX7HQEggL7//ntdcsklys7OVnR0tO3zHzt2rM/1Kk5l1KhR6tWrlx588EHbawAaOgZvAqg3evbsqRkzZignJ0c9evSwdd7GGGVlZenzzz8/bbvS0lL16NFDf/jDH2xdPtBY0GMBAABswxgLAABgG4IFAACwDcECAADYhmABAABsQ7AAAAC2IVgAAADbECwAAIBtCBYAAMA2BAsAAGCb/wfoltKR/8U/3wAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# ============================================================\n", "# BUILD RAW AND CLEAN DATASETS FOR DIAGNOSTIC\n", "# ============================================================\n", "\n", "def build_accounting_df(stocks_df, flows_df):\n", "\n", " keys = [\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\"\n", " ]\n", "\n", " df = stocks_df.merge(flows_df, on=keys, how=\"left\")\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(keys)\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"delta_aum\"] = df[\"Quantity - AUM\"] - df[\"prev_aum\"]\n", "\n", " return df\n", "\n", "\n", "# RAW DATASET\n", "df_raw = build_accounting_df(stocks, flows)\n", "\n", "# CLEAN DATASET\n", "df_clean = build_accounting_df(stocks_repaired, flows)\n", "\n", "\n", "# ============================================================\n", "# SCATTER FUNCTION\n", "# ============================================================\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "def accounting_scatter(df, title):\n", "\n", " diag = df[\n", " df[\"prev_aum\"].notna() &\n", " df[\"flow_lag\"].notna()\n", " ]\n", "\n", " sample = diag.sample(20000, random_state=1)\n", "\n", " plt.figure(figsize=(6,6))\n", "\n", " plt.scatter(\n", " sample[\"flow_lag\"],\n", " sample[\"delta_aum\"],\n", " alpha=0.3,\n", " s=5\n", " )\n", "\n", " x = np.linspace(\n", " sample[\"flow_lag\"].min(),\n", " sample[\"flow_lag\"].max(),\n", " 100\n", " )\n", "\n", " plt.plot(x, x, color=\"red\", label=\"Perfect identity\")\n", "\n", " plt.xlabel(\"Flow (t-1,t)\")\n", " plt.ylabel(\"Δ AUM\")\n", "\n", " plt.title(title)\n", "\n", " plt.legend()\n", "\n", " plt.show()\n", "\n", "\n", "# ============================================================\n", "# PLOTS\n", "# ============================================================\n", "\n", "accounting_scatter(df_raw, \"Raw dataset\")\n", "accounting_scatter(df_clean, \"Clean dataset\")" ] }, { "cell_type": "code", "execution_count": 56, "id": "dc3ba2ce-42ca-490a-98b0-83fd14457dd8", "metadata": {}, "outputs": [ { "data": { "image/png": "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gYyMDOlzampqoeslIiIioreLrJ5mkJaWht69e2PFihWws7PTap3p06fDyspK+nJxcSnmKomIiIiopOh1ZtbOzg6GhoZISkrSaE9KSoKDg0Ou/levXsX169fRvn17qS3nUSJGRka4ePEiKlWqpLFOQEAA/P39pc+pqakMtERERETvCL2GWaVSiXr16iEiIgIdO3YE8CKcRkREYNiwYbn6e3h44MyZMxptP/zwA9LS0rBgwYI8Q6pKpYJKpSqW+omIiIhIv/T+NAN/f3/07dsX9evXR8OGDRESEoLHjx9LTzfo06cPnJ2dMX36dJiYmKBmzZoa61tbWwNArnYiIiIievfpPcx269YN9+7dw8SJE5GYmAhPT0/s3LlTuiksPj4eBgayurSXiIiIiEqIQhTVu8RkIjU1FVZWVkhJSYGlpWWJbNNt3F8lsp23xXWTnvouoeQEpei7AiIioneOLnmNU55EREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWzqH2Zs3b+LWrVvS52PHjmHkyJFYvnx5kRZGRERERPQ6OofZnj17Yt++fQCAxMREfPLJJzh27BjGjx+P4ODgIi+QiIiIiCg/OofZs2fPomHDhgCATZs2oWbNmjh69CjWr1+P0NDQoq6PiIiIiChfOofZ58+fQ6VSAQD27NmDDh06AAA8PDxw586doq2OiIiIiKgAOofZGjVqYOnSpTh06BB2794NX19fAEBCQgJKly5d5AUSEREREeVH5zA7c+ZMLFu2DC1atECPHj1Qu3ZtAMD27dulyw+IiIiIiEqC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mGffa9euFWmBRERERET50SrMLl++HJ07d8aVK1cwfPhwDBw4EBYWFsVdGxERERFRgbR+aULO271OnjyJESNGMMwSERERkd7p/AawNWvWSH++desWAKBcuXJFVxERERERkZZ0fs6sWq1GcHAwrKys4OrqCldXV1hbW2Py5Mm8MYyIiIiISpTOM7Pjx4/HqlWrMGPGDDRt2hQAcPjwYQQFBeHZs2eYOnVqkRdJRERERJQXncPs2rVrsXLlSnTo0EFqq1WrFpydnTFkyBCGWSIiIiIqMTpfZvDw4UN4eHjkavfw8MDDhw+LpCgiIiIiIm3oHGZr166NRYsW5WpftGgRateuXSRFERERERFpQ+fLDGbNmoV27dphz549aNy4MQAgMjISN2/exI4dO4q8QCIiIiKi/Og8M+vl5YVLly6hU6dOePToER49eoTOnTvj4sWLaN68eXHUSERERESUJ51nZgHAycmJN3oRERERkd7pPDNLRERERPS2YJglIiIiItlimCUiIiIi2WKYJSIiIiLZKlSYzcrKwp49e7Bs2TKkpaUBABISEpCenl6kxRERERERFUTnpxncuHEDvr6+iI+PR0ZGBj755BNYWFhg5syZyMjIwNKlS4ujTiIiIiKiXHSemR0xYgTq16+P5ORkmJqaSu2dOnVCREREkRZHRERERFQQnWdmDx06hKNHj0KpVGq0u7m54fbt20VWGBERERHR6+g8M6tWq5GdnZ2r/datW7CwsCiSooiIiIiItKFzmG3dujVCQkKkzwqFAunp6QgMDETbtm2LsjYiIiIiogLpfJnBnDlz4Ovri+rVq+PZs2fo2bMnLl++DDs7O2zYsKE4aiQiIiIiypPOYdbFxQWnT59GWFgYTp8+jfT0dPTv3x+9evXSuCGMiIiIiKi46RRmnz9/Dg8PD4SHh6NXr17o1atXcdVFRERERPRaOl0za2xsjGfPnhVXLUREREREOtH5BrChQ4di5syZyMrKKo56iIiIiIi0pnOYPX78OP744w+UL18ePj4+6Ny5s8ZXYSxevBhubm4wMTFBo0aNcOzYsXz7rlixAs2bN4eNjQ1sbGzg7e1dYH8iIiIienfpHGatra3RpUsX+Pj4wMnJCVZWVhpfugoLC4O/vz8CAwNx6tQp1K5dGz4+Prh7926e/ffv348ePXpg3759iIyMhIuLC1q3bs0XNhARERG9hxRCCKHPAho1aoQGDRpg0aJFAF68lMHFxQXffPMNxo0b99r1s7OzYWNjg0WLFqFPnz6v7Z+amgorKyukpKTA0tLyjevXhtu4v0pkO2+L6yY99V1CyQlK0ctm36dziucTEdH7R5e8pvPMbFHKzMzEyZMn4e3tLbUZGBjA29sbkZGRWo3x5MkTPH/+HLa2tnkuz8jIQGpqqsYXEREREb0bdH7OLABs3rwZmzZtQnx8PDIzMzWWnTp1Sutx7t+/j+zsbNjb22u029vb48KFC1qNMXbsWDg5OWkE4pdNnz4dkyZN0romIiIiIpIPnWdmFy5cCD8/P9jb2yMqKgoNGzZE6dKlce3aNbRp06Y4aszXjBkzsHHjRmzZsgUmJiZ59gkICEBKSor0dfPmzRKtkYiIiIiKj85h9qeffsLy5cvx448/QqlUYsyYMdi9ezeGDx+OlBTdrveys7ODoaEhkpKSNNqTkpLg4OBQ4Lpz5szBjBkzsGvXLtSqVSvffiqVCpaWlhpfRERERPRu0DnMxsfHo0mTJgAAU1NTpKWlAQB69+6NDRs26DSWUqlEvXr1EBERIbWp1WpERESgcePG+a43a9YsTJ48GTt37kT9+vV13QUiIiIiekfoHGYdHBzw8OFDAED58uXx77//AgDi4uJQmAcj+Pv7Y8WKFVi7di1iY2Px9ddf4/Hjx/Dz8wMA9OnTBwEBAVL/mTNnYsKECVi9ejXc3NyQmJiIxMREpKen67xtIiIiIpI3nW8A+/jjj7F9+3bUqVMHfn5++Pbbb7F582acOHGiUC9N6NatG+7du4eJEyciMTERnp6e2Llzp3RTWHx8PAwM/j9zL1myBJmZmfj88881xgkMDERQUJDO2yciIiIi+dL5ObNqtRpqtRpGRi9y8MaNG3H06FG4u7vjq6++glKpLJZCiwqfM1v8+FzQ4vc+nVM8n4iI3j+65DWdZ2YNDAw0Zkq7d++O7t27614lEREREdEbKtRzZh89eoRjx47h7t27UKvVGsu0eQsXEREREVFR0DnM/vnnn+jVqxfS09NhaWkJhUIhLVMoFAyzRERERFRidH6awahRo/Dll18iPT0djx49QnJysvSV85QDIiIiIqKSoHOYvX37NoYPHw4zM7PiqIeIiIiISGs6h1kfHx+cOHGiOGohIiIiItKJVtfMbt++Xfpzu3btMHr0aJw/fx4ffPABjI2NNfp26NChaCskIiIiIsqHVmG2Y8eOudqCg4NztSkUCmRnZ79xUURERERE2tAqzL76+C0iIiIioreBztfMEhERERG9LbQOs5GRkQgPD9do+/nnn1GhQgWULVsWgwYNQkZGRpEXSERERESUH63DbHBwMM6dOyd9PnPmDPr37w9vb2+MGzcOf/75J6ZPn14sRRIRERER5UXrMBsdHY1WrVpJnzdu3IhGjRphxYoV8Pf3x8KFC7Fp06ZiKZKIiIiIKC9ah9nk5GTY29tLnw8cOIA2bdpInxs0aICbN28WbXVERERERAXQOsza29sjLi4OAJCZmYlTp07hww8/lJanpaXleuYsEREREVFx0jrMtm3bFuPGjcOhQ4cQEBAAMzMzNG/eXFoeExODSpUqFUuRRERERER50eo5swAwefJkdO7cGV5eXjA3N8fatWuhVCql5atXr0br1q2LpUgiIiIiorxoHWbt7Oxw8OBBpKSkwNzcHIaGhhrLf/vtN5ibmxd5gURERERE+dE6zOawsrLKs93W1vaNiyEiIiIi0gXfAEZEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLJlpO8CiIiISkyQlb4rKFlBKfqugKjYMcwSERERFdb79APSW/rDEcMsERERFRm3cX/pu4QSdd1E3xUQr5klIiIiItnizCwR0XvufZpJ4ywa0buHM7NEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFt6D7OLFy+Gm5sbTExM0KhRIxw7dizfvufOnUOXLl3g5uYGhUKBkJCQkiuUiIiIiN46eg2zYWFh8Pf3R2BgIE6dOoXatWvDx8cHd+/ezbP/kydPULFiRcyYMQMODg4lXC0RERERvW30GmbnzZuHgQMHws/PD9WrV8fSpUthZmaG1atX59m/QYMGmD17Nrp37w6VSlXC1RIRERHR20ZvYTYzMxMnT56Et7f3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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# ============================================================\n", "# BUILD RAW AND CLEAN ACCOUNTING DATASETS\n", "# ============================================================\n", "\n", "def build_gap_df(stocks_df, flows_df):\n", "\n", " keys = [\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\"\n", " ]\n", "\n", " df = stocks_df.merge(flows_df, on=keys, how=\"left\")\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(keys)\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " .fillna(0)\n", " )\n", "\n", " df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", " df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", " df[\"rupture\"] = df[\"gap\"].abs() > 10\n", "\n", " return df\n", "\n", "\n", "# RAW DATA\n", "df_raw = build_gap_df(stocks, flows)\n", "\n", "# CLEAN DATA\n", "df_clean = build_gap_df(stocks_repaired, flows)\n", "\n", "\n", "# ============================================================\n", "# DISTRIBUTION OF RUPTURE RATES\n", "# ============================================================\n", "\n", "def rupture_distribution(df):\n", "\n", " series = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " .agg(\n", " n_ruptures=(\"rupture\",\"sum\"),\n", " total_obs=(\"rupture\",\"count\")\n", " )\n", " .reset_index()\n", " )\n", "\n", " series[\"rupture_rate\"] = series[\"n_ruptures\"] / series[\"total_obs\"]\n", "\n", " bins = [0,0.01,0.10,0.30,1]\n", " labels = [\"≤1%\", \"1–10%\", \"10–30%\", \">30%\"]\n", "\n", " series[\"class\"] = pd.cut(\n", " series[\"rupture_rate\"],\n", " bins=bins,\n", " labels=labels,\n", " include_lowest=True\n", " )\n", "\n", " dist = (\n", " series[\"class\"]\n", " .value_counts(normalize=True)\n", " .sort_index()\n", " )\n", "\n", " return dist\n", "\n", "\n", "dist_before = rupture_distribution(df_raw)\n", "dist_after = rupture_distribution(df_clean)\n", "\n", "\n", "# ============================================================\n", "# PLOT\n", "# ============================================================\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "x = np.arange(len(dist_before))\n", "\n", "plt.figure(figsize=(8,4))\n", "\n", "plt.bar(x-0.2, dist_before.values, width=0.4, label=\"Before cleaning\")\n", "plt.bar(x+0.2, dist_after.values, width=0.4, label=\"After cleaning\")\n", "\n", "plt.xticks(x, dist_before.index)\n", "\n", "plt.ylabel(\"Share of series\")\n", "\n", "plt.title(\"Distribution of accounting rupture rates\")\n", "\n", "plt.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 59, "id": "8a52078b-5d56-447a-aabe-712e2c74cdae", "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAArMAAAHWCAYAAABkNgFvAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAZPJJREFUeJzt3XdUFNf/PvBnQXbpIIogiIBij4JCQDSKBcUSu9EYo0jUJAZbiIkhRezEWIK9K0ZjJLHmq8aGYMUGYouiYsECKBYQVED2/v7wx3xcKQIiy+jzOmfPYe/eufPenRl5HO7MKoQQAkREREREMqSj7QKIiIiIiEqKYZaIiIiIZIthloiIiIhki2GWiIiIiGSLYZaIiIiIZIthloiIiIhki2GWiIiIiGSLYZaIiIiIZIthloiIiIhki2GWqJSMHz8eCoWiTNbVqlUrtGrVSnoeGRkJhUKB9evXl8n6Bw0aBAcHhzJZV0mlp6djyJAhsLa2hkKhwOjRo7VdUpGV5b5EZefatWtQKBQIDQ3VdilEbxWGWaJ8hIaGQqFQSA99fX3Y2NjAx8cHc+bMwaNHj0plPbdv38b48eMRGxtbKuOVpvJcW1FMnToVoaGhGDZsGFavXo0BAwZou6Q3qqy319SpU7F58+YyWZfcrF27FiEhIdoug+idoRBCCG0XQVTehIaGws/PDxMnToSjoyOys7ORlJSEyMhI7N69G9WrV8c///yDRo0aScs8e/YMz549g76+fpHXc+LECbz//vtYuXIlBg0aVOTlsrKyAABKpRLA8zOzrVu3xt9//43evXsXeZyS1padnQ21Wg2VSlUq63oTmjZtigoVKuDgwYPaLqXYynJfKiljY2P07t2bZxnz8eGHH+Ls2bO4du2aRrsQApmZmdDT04Ourq52iiN6C1XQdgFE5VnHjh3h5uYmPQ8MDMTevXvx4YcfomvXrjh//jwMDAwAABUqVECFCm/2kHr8+DEMDQ2lEKstenp6Wl1/Udy5cwf169fXdhmSp0+fQqlUQkfn1X8QK4t9icpe7l95iKh0cZoBUTG1adMGP//8M65fv441a9ZI7fnNc9y9ezc++OADmJubw9jYGHXq1MEPP/wA4PnZ1Pfffx8A4OfnJ01pyD3T1apVK7z33nuIjo5Gy5YtYWhoKC378pzZXDk5Ofjhhx9gbW0NIyMjdO3aFTdu3NDo4+DgkO+ZuxfHfFVt+c2ZzcjIwDfffAM7OzuoVCrUqVMHM2bMwMt//FEoFBg+fDg2b96M9957DyqVCg0aNMCOHTvy/8BfcufOHQwePBhWVlbQ19eHs7MzVq1aJb2eO3/46tWr2LZtm1T7y2fJ8qvpjz/+QJ06daCvrw9XV1fs378/T99bt27hs88+g5WVlVT7ihUrNPrk1rBu3Tr89NNPsLW1haGhIdLS0pCdnY0JEyagVq1a0NfXR6VKlfDBBx9g9+7d0vKlvS8BwNGjR9GhQweYmZnB0NAQXl5eOHTokMY6ctd7+fJlDBo0CObm5jAzM4Ofnx8eP36s8XllZGRg1apV0roKOxuclZWFcePGwdXVFWZmZjAyMkKLFi0QERGRp69arcbs2bPRsGFD6Ovrw9LSEh06dMCJEyc0+q1Zswbu7u4wNDRExYoV0bJlS+zatUujz4IFC9CgQQOoVCrY2NjA398fDx8+1OhTlOMh9zNWKBT466+/MGXKFFSrVg36+vpo27YtLl++rLHctm3bcP36demzyT1W8pszO2jQIBgbG+PWrVvo3r07jI2NYWlpiTFjxiAnJ0ejpnv37mHAgAEwNTWFubk5fH19cerUqSLPwz19+jS8vLxgYGCAatWqYfLkyVi5cmWe42PLli3o3LkzbGxsoFKpULNmTUyaNClPPS/+G9WsWTMYGBjA0dERixYtyrPuuXPnokGDBtL2cnNzw9q1a19ZM1FR8L/+RCUwYMAA/PDDD9i1axeGDh2ab59z587hww8/RKNGjTBx4kSoVCpcvnxZChD16tXDxIkTMW7cOHz++edo0aIFAKBZs2bSGPfu3UPHjh3x8ccf49NPP4WVlVWhdU2ZMgUKhQJjx47FnTt3EBISAm9vb8TGxkpnkIuiKLW9SAiBrl27IiIiAoMHD4aLiwt27tyJb7/9Frdu3cJvv/2m0f/gwYPYuHEjvvrqK5iYmGDOnDno1asXEhISUKlSpQLrevLkCVq1aoXLly9j+PDhcHR0xN9//41Bgwbh4cOHGDVqFOrVq4fVq1fj66+/RrVq1fDNN98AACwtLQt9z/v27UNYWBhGjhwJlUqFBQsWoEOHDjh27Bjee+89AEBycjKaNm0qhV9LS0v8+++/GDx4MNLS0vJcZDZp0iQolUqMGTMGmZmZUCqVGD9+PIKDgzFkyBC4u7sjLS0NJ06cQExMDNq1a5dvba+7L+3duxcdO3aEq6srgoKCoKOjg5UrV6JNmzY4cOAA3N3dNdbXp08fODo6Ijg4GDExMVi2bBmqVKmCadOmAQBWr14t1f/5558DAGrWrFngZ5uWloZly5ahX79+GDp0KB49eoTly5fDx8cHx44dg4uLi9R38ODBCA0NRceOHTFkyBA8e/YMBw4cwJEjR6S/kkyYMAHjx49Hs2bNMHHiRCiVShw9ehR79+5F+/btATwP5hMmTIC3tzeGDRuGuLg4LFy4EMePH8ehQ4dK/NeFX375BTo6OhgzZgxSU1Px66+/on///jh69CgA4Mcff0Rqaipu3rwp7ffGxsaFjpmTkwMfHx94eHhgxowZ2LNnD2bOnImaNWti2LBhAJ6H/C5duuDYsWMYNmwY6tatiy1btsDX17dIdd+6dQutW7eGQqFAYGAgjIyMsGzZsnynCoWGhsLY2BgBAQEwNjbG3r17MW7cOKSlpWH69OkafR88eIBOnTqhT58+6NevH/766y8MGzYMSqUSn332GQBg6dKlGDlyJHr37o1Ro0bh6dOnOH36NI4ePYpPPvmkSPUTFUoQUR4rV64UAMTx48cL7GNmZiYaN24sPQ8KChIvHlK//fabACDu3r1b4BjHjx8XAMTKlSvzvObl5SUAiEWLFuX7mpeXl/Q8IiJCABC2trYiLS1Nav/rr78EADF79mypzd7eXvj6+r5yzMJq8/X1Ffb29tLzzZs3CwBi8uTJGv169+4tFAqFuHz5stQGQCiVSo22U6dOCQBi7ty5edb1opCQEAFArFmzRmrLysoSnp6ewtjYWOO929vbi86dOxc63os1ARAnTpyQ2q5fvy709fVFjx49pLbBgweLqlWripSUFI3lP/74Y2FmZiYeP34shPjf9qhRo4bUlsvZ2fmVdZXmvqRWq0WtWrWEj4+PUKvVUvvjx4+Fo6OjaNeuXZ71fvbZZxpj9OjRQ1SqVEmjzcjIKN/9KD/Pnj0TmZmZGm0PHjwQVlZWGuvau3evACBGjhyZZ4zc2i9duiR0dHREjx49RE5OTr597ty5I5RKpWjfvr1Gn3nz5gkAYsWKFVJbUY+H3G1ar149jfcye/ZsAUCcOXNGauvcubPG8ZHr6tWrebaRr6+vACAmTpyo0bdx48bC1dVVer5hwwYBQISEhEhtOTk5ok2bNgUepy8aMWKEUCgU4uTJk1LbvXv3hIWFhQAgrl69KrW/vM8KIcQXX3whDA0NxdOnT6W23H+jZs6cKbVlZmYKFxcXUaVKFZGVlSWEEKJbt26iQYMGhdZH9Do4zYCohIyNjQu9q4G5uTmA53+yU6vVJVqHSqWCn59fkfsPHDgQJiYm0vPevXujatWq2L59e4nWX1Tbt2+Hrq4uRo4cqdH+zTffQAiBf//9V6Pd29tb40xeo0aNYGpqiitXrrxyPdbW1ujXr5/Upqenh5EjRyI9PR379u0r8Xvw9PSEq6ur9Lx69ero1q0bdu7ciZycHAghsGHDBnTp0gVCCKSkpEgPHx8fpKamIiYmRmNMX1/fPGfEzc3Nce7cOVy6dKnItb3OvhQbG4tLly7hk08+wb1796SaMzIy0LZtW+zfvz/PmF9++aXG8xYtWuDevXtIS0sr1rpz6erqSvO81Wo17t+/j2fPnsHNzU3jM9uwYQMUCgWCgoLyjJE77WLz5s1Qq9UYN25cnvnHuX327NmDrKwsjB49WqPP0KFDYWpqim3btpXofQDPp3G8OGc99yz4q/bdV8nvM39xzB07dkBPT0/jL0E6Ojrw9/cv0vg7duyAp6enxllwCwsL9O/fP0/fF/fZR48eISUlBS1atMDjx49x4cIFjb4VKlTAF198IT1XKpX44osvcOfOHURHRwN4vv/evHkTx48fL1KtRMXFMEtUQunp6RrB8WV9+/ZF8+bNMWTIEFhZWeHjjz/GX3/9VawwYmtrW6yLvWrVqqXxXKFQwMnJqdD5oqXh+vXrsLGxyfN51KtXT3r9RdWrV88zRsWKFfHgwYNXrqdWrVp5QkxB6ymOlz87AKhduzYeP36Mu3fv4u7du3j48CGWLFkCS0tLjUfufzju3Lmjsbyjo2OeMSdOnIiHDx+idu3aaNiwIb799lucPn260NpeZ1/KDc2+vr556l62bBkyMzORmpqqsczL26dixYoA8MrtU5hVq1ahUaNG0jxhS0tLbNu2TWPd8fHxsLGxgYWFRYHjxMfHQ0dHp9CL+3L3gzp16mi0K5VK1KhR47X2kzfx2eTODX553BfHvH79OqpWrQpDQ0ONfk5OTkVax/Xr1/Ptm1/buXPn0KNHD5iZmcHU1BSWlpb49NNPASDPvmJjYwMjIyONttq1awOA9O/O2LFjYWxsDHd3d9SqVQv+/v555msTvQ7OmSUqgZs3byI1NbXQXyQGBgbYv38/IiIisG3bNuzYsQNhYWFo06YNdu3aVaRb8xRnnmtRFXQz/pycnDK7XVBB6xHl+E6BucHx008/LXCe4ou3agPy334tW7ZEfHw8tmzZgl27dmHZsmX47bffsGjRIgwZMiTfcV9nX8qte/r06Rpn5V708pzO0t4+a9aswaBBg9C9e3d8++23qFKlCnR1dREcHIz4+PgSjVlains8vIl9tzzdpuvhw4fw8vKCqakpJk6ciJo1a0JfXx8xMTEYO3Zsif7KVK9ePcTFxWHr1q3YsWMHNmzYgAULFmDcuHGYMGHCG3gX9K5hmCUqgdWrVwMAfHx8Cu2no6ODtm3bom3btpg1axamTp2KH3/8EREREfD29i71b3l6+U/XQghcvnxZI2RVrFgxzxXdwPMzNzVq1JCeF6c2e3t77NmzB48ePdI4O5v7J0l7e/sij/Wq9Zw+fRpqtVrj7GxprCe/P/tfvHgRhoaG0lkzExMT5OTkwNvbu8TrAZ7/edfPzw9+fn5IT09Hy5YtMX78+ALDLFDyfSl3Ooepqelr1/2i4uwf69evR40aNbBx40aN5V6eTlCzZk3s3LkT9+/fL/DsbM2aNaFWq/Hff/8VGM5z94O4uDiNfTorKwtXr17V+ByKejwUx5v49jZ7e3tERERIt+fL9eKdFF61fH59X26LjIzEvXv3sHHjRrRs2VJqv3r1ar7j3r59GxkZGRpnZy9evAgAGnc8MTIyQt++fdG3b19kZWWhZ8+emDJlCgIDA3m7MnptnGZAVEx79+7FpEmT4OjomO98s1z379/P05b7yzczMxMApF8A+f0yLYnff/9dYx7v+vXrkZiYiI4dO0ptNWvWxJEjR6QvXgCArVu35rmFV3Fq69SpE3JycjBv3jyN9t9++w0KhUJj/a+jU6dOSEpKQlhYmNT27NkzzJ07F8bGxvDy8irx2FFRURrzN2/cuIEtW7agffv20NXVha6uLnr16oUNGzbg7NmzeZa/e/dukdZz7949jefGxsZwcnKS9on8vM6+5Orqipo1a2LGjBlIT08vcd0vMzIyKvJ+m3vm8cWzl0ePHkVUVJRGv169ekEIke/Zutxlu3fvDh0dHUycODHPWcLcPt7e3lAqlZgzZ47GOpcvX47U1FR07txZaivq8VAcRkZGef4c/7p8fHyQnZ2NpUuXSm1qtRrz588v8vJRUVEa3xB3//59/PHHHxr98ttWWVlZWLBgQb7jPnv2DIsXL9bou3jxYlhaWkpz0F/e55VKJerXrw8hBLKzs4tUP1FheGaWqBD//vsvLly4gGfPniE5ORl79+7F7t27YW9vj3/++afQMwoTJ07E/v370blzZ9jb2+POnTtYsGABqlWrhg8++ADA81+k5ubmWLRoEUxMTGBkZAQPD49851oWhYWFBT744AP4+fkhOTkZISEhcHJy0rhoZMiQIVi/fj06dOiAPn36ID4+HmvWrMlza6Xi1NalSxe0bt0aP/74I65duwZnZ2fs2rULW7ZswejRowu9bVNxfP7551i8eDEGDRqE6OhoODg4YP369Th06BBCQkIKncP8Ku+99x58fHw0bs0FQCNY/fLLL4iIiICHhweGDh2K+vXr4/79+4iJicGePXvyDZ0vq1+/Plq1agVXV1dYWFjgxIkTWL9+PYYPH17gMq+7Ly1btgwdO3ZEgwYN4OfnB1tbW9y6dQsREREwNTXF//3f/xX783J1dcWePXswa9Ys2NjYwNHRER4eHvn2/fDDD7Fx40b06NEDnTt3xtWrV7Fo0SLUr19fI2C3bt0aAwYMwJw5c3Dp0iV06NABarUaBw4cQOvWrTF8+HA4OTnhxx9/xKRJk9CiRQv07NkTKpUKx48fh42NDYKDg2FpaYnAwEBMmDABHTp0QNeuXREXF4cFCxbg/fffl+Z/AkU/Hor72YSFhSEgIADvv/8+jI2N0aVLlxKPBzwP8e7u7vjmm29w+fJl1K1bF//884+0z73qbPB3332HNWvWoF27dhgxYoR0a67q1avj/v370vLNmjVDxYoV4evri5EjR0KhUGD16tUFTqOwsbHBtGnTcO3aNdSuXRthYWGIjY3FkiVLpNuftW/fHtbW1mjevDmsrKxw/vx5zJs3D507d36tY5ZIooU7KBCVe7m35sp9KJVKYW1tLdq1aydmz56tcQuoXC/fTik8PFx069ZN2NjYCKVSKWxsbES/fv3ExYsXNZbbsmWLqF+/vqhQoYLGLXa8vLwKvJ1NQbcN+vPPP0VgYKCoUqWKMDAwEJ07dxbXr1/Ps/zMmTOFra2tUKlUonnz5uLEiRN5xiystpdvzSWEEI8ePRJff/21sLGxEXp6eqJWrVpi+vTpGreDEuL5bbD8/f3z1FTQLZJelpycLPz8/ETlypWFUqkUDRs2zPe2RMW9NZe/v79Ys2aNqFWrllCpVKJx48YiIiIi3/X7+/sLOzs7oaenJ6ytrUXbtm3FkiVLpD652+Pvv//Os/zkyZOFu7u7MDc3FwYGBqJu3bpiypQp0m2MhCj9fUkIIU6ePCl69uwpKlWqJFQqlbC3txd9+vQR4eHhedb78i3Aco+HF2/fdOHCBdGyZUthYGAgABS67dRqtZg6daqwt7eXPtutW7fmux89e/ZMTJ8+XdStW1colUphaWkpOnbsKKKjozX6rVixQjRu3FioVCpRsWJF4eXlJXbv3q3RZ968eaJu3bpCT09PWFlZiWHDhokHDx7kqa8ox0NB2zS/222lp6eLTz75RJibmwsA0nss6NZcRkZGeWp6eR8QQoi7d++KTz75RJiYmAgzMzMxaNAgcejQIQFArFu3Ls8YLzt58qRo0aKFUKlUolq1aiI4OFjMmTNHABBJSUlSv0OHDommTZsKAwMDYWNjI7777juxc+dOAUDjmMj9N+rEiRPC09NT6OvrC3t7ezFv3jyN9S5evFi0bNlS2vdq1qwpvv32W5GamvrKmomKQiFEOb7igoioDCgUCvj7++eZJkFU3m3evBk9evTAwYMH0bx582IvP3r0aCxevBjp6enFvhCtVatWSElJyXfaDVFZ4pxZIiIiGXjy5InG85ycHMydOxempqZo0qRJsZe/d+8eVq9ejQ8++KBc3VGBqLg4Z5aIiEgGRowYgSdPnsDT0xOZmZnYuHEjDh8+jKlTpxbpNn6enp5o1aoV6tWrh+TkZCxfvhxpaWn4+eefy6B6ojeHYZaIiEgG2rRpg5kzZ2Lr1q14+vQpnJycMHfu3EIvHnxRp06dsH79eixZsgQKhQJNmjTB8uXLNW7BRSRHnDNLRERERLLFObNEREREJFsMs0REREQkW+/cnFm1Wo3bt2/DxMTkjXzlIBERERG9HiEEHj16BBsbG42vL8/POxdmb9++DTs7O22XQURERESvcOPGDVSrVq3QPu9cmM396rwbN27A1NRUy9UQERER0cvS0tJgZ2dXpK88fufCbO7UAlNTU4ZZIiIionKsKFNCeQEYEREREckWwywRERERyRbDLBERERHJ1js3Z5aIiIhKR05ODrKzs7VdBsmUnp4edHV1X3schlkiIiIqtvT0dNy8eRNCCG2XQjKlUChQrVo1GBsbv9Y4DLNERERULDk5Obh58yYMDQ1haWnJLyGiYhNC4O7du7h58yZq1ar1WmdoGWaJiIioWLKzsyGEgKWlJQwMDLRdDsmUpaUlrl27huzs7NcKs7wAjIiIiEqEZ2TpdZTW/sMwS0RERESyxTBLRERERLLFMEtERESkZQqFAps3b9Z2GbLEC8CIiIioVPy2+2KZru/rdrWL1X/QoEFYtWoVAKBChQqoVq0aPvroI0ycOBH6+vpvosQ3Zvz48di8eTNiY2PLdL2tWrWCi4sLQkJCynS9hWGYJSIiondGhw4dsHLlSmRnZyM6Ohq+vr5QKBSYNm2atkujEuI0AyIiInpnqFQqWFtbw87ODt27d4e3tzd2794tvX7v3j3069cPtra2MDQ0RMOGDfHnn39Kr2/duhXm5ubIyckBAMTGxkKhUOD777+X+gwZMgSffvppgTVcunQJLVu2hL6+PurXr6+x/lxjx45F7dq1YWhoiBo1auDnn3+Wvm0tNDQUEyZMwKlTp6BQKKBQKBAaGgoAmDVrFho2bAgjIyPY2dnhq6++Qnp6ujTu9evX0aVLF1SsWBFGRkZo0KABtm/fLr1+9uxZdOzYEcbGxrCyssKAAQOQkpIC4PmZ7X379mH27NnSeq9du1aMT//NYJglIiKid9LZs2dx+PBhKJVKqe3p06dwdXXFtm3bcPbsWXz++ecYMGAAjh07BgBo0aIFHj16hJMnTwIA9u3bh8qVKyMyMlIaY9++fWjVqlW+61Sr1ejZsyeUSiWOHj2KRYsWYezYsXn6mZiYIDQ0FP/99x9mz56NpUuX4rfffgMA9O3bF9988w0aNGiAxMREJCYmom/fvgAAHR0dzJkzB+fOncOqVauwd+9efPfdd9K4/v7+yMzMxP79+3HmzBlMmzZN+gauhw8fok2bNmjcuDFOnDiBHTt2IDk5GX369AEAzJ49G56enhg6dKi0Xjs7uxJ++qWH0wzKQNTyMaU+pufgGaU+JhER0dtu69atMDY2xrNnz5CZmQkdHR3MmzdPet3W1hZjxvzv9/aIESOwc+dO/PXXX3B3d4eZmRlcXFwQGRkJNzc3REZG4uuvv8aECROQnp6O1NRUXL58GV5eXvmuf8+ePbhw4QJ27twJGxsbAMDUqVPRsWNHjX4//fST9LODgwPGjBmDdevW4bvvvoOBgQGMjY1RoUIFWFtbayw3evRojeUmT56ML7/8EgsWLAAAJCQkoFevXmjYsCEAoEaNGlL/efPmoXHjxpg6darUtmLFCtjZ2eHixYuoXbs2lEolDA0N86xXmxhmiYiI6J3RunVrLFy4EBkZGfjtt99QoUIF9OrVS3o9JycHU6dOxV9//YVbt24hKysLmZmZMDQ0lPp4eXkhMjIS33zzDQ4cOIDg4GD89ddfOHjwIO7fvw8bGxvUqlUr3/WfP38ednZ2UpAFAE9Pzzz9wsLCMGfOHMTHxyM9PR3Pnj2DqanpK9/fnj17EBwcjAsXLiAtLQ3Pnj3D06dP8fjxYxgaGmLkyJEYNmwYdu3aBW9vb/Tq1QuNGjUCAJw6dQoRERHSmdoXxcfHo3bt4l1wV1Y4zYCIiIjeGUZGRnBycoKzszNWrFiBo0ePYvny5dLr06dPx+zZszF27FhEREQgNjYWPj4+yMrKkvq0atUKBw8exKlTp6Cnp4e6deuiVatWiIyMxL59+wo8K1tUUVFR6N+/Pzp16oStW7fi5MmT+PHHHzVqyM+1a9fw4YcfolGjRtiwYQOio6Mxf/58AJCWHTJkCK5cuYIBAwbgzJkzcHNzw9y5cwEA6enp6NKlC2JjYzUeuXN8yyuGWSIiInon6ejo4IcffsBPP/2EJ0+eAAAOHTqEbt264dNPP4WzszNq1KiBixc1bzmWO2/2t99+k4JrbpiNjIwscL4sANSrVw83btxAYmKi1HbkyBGNPocPH4a9vT1+/PFHuLm5oVatWrh+/bpGH6VSKV2Elis6OhpqtRozZ85E06ZNUbt2bdy+fTtPDXZ2dvjyyy+xceNGfPPNN1i6dCkAoEmTJjh37hwcHBzg5OSk8TAyMipwvdrGMEtERETvrI8++gi6urrSGcxatWph9+7dOHz4MM6fP48vvvgCycnJGstUrFgRjRo1wh9//CEF15YtWyImJgYXL14s9Myst7c3ateuDV9fX5w6dQoHDhzAjz/+qNGnVq1aSEhIwLp16xAfH485c+Zg06ZNGn0cHBxw9epVxMbGIiUlBZmZmXByckJ2djbmzp2LK1euYPXq1Vi0aJHGcqNHj8bOnTtx9epVxMTEICIiAvXq1QPw/OKw+/fvo1+/fjh+/Dji4+Oxc+dO+Pn5SQHWwcEBR48exbVr15CSkgK1Wl38D72Ucc4sERERlYrifolBeVChQgUMHz4cv/76K4YNG4affvoJV65cgY+PDwwNDfH555+je/fuSE1N1VjOy8sLsbGxUpi1sLBA/fr1kZycjDp16hS4Ph0dHWzatAmDBw+Gu7s7HBwcMGfOHHTo0EHq07VrV3z99dcYPnw4MjMz0blzZ/z8888YP3681KdXr17YuHEjWrdujYcPH2LlypUYNGgQZs2ahWnTpiEwMBAtW7ZEcHAwBg4cKC2Xk5MDf39/3Lx5E6ampujQoYN0lwQbGxscOnQIY8eORfv27ZGZmQl7e3t06NABOjrPz3+OGTMGvr6+qF+/Pp48eYKrV6/CwcHhNbfC61EIIYRWKyhjaWlpMDMzQ2pqapEmUpcG3s2AiIjeJk+fPsXVq1fh6Ogou2/OovKjsP2oOHmN0wyIiIiISLYYZomIiIhIthhmiYiIiEi2GGaJiIiISLa0Hmbnz58PBwcH6Ovrw8PDQ/ru44KEhISgTp06MDAwgJ2dHb7++ms8ffq0jKolIiIiovJEq2E2LCwMAQEBCAoKQkxMDJydneHj44M7d+7k23/t2rX4/vvvERQUhPPnz2P58uUICwvDDz/8UMaVExEREVF5oNUwO2vWLAwdOhR+fn6oX78+Fi1aBENDQ6xYsSLf/ocPH0bz5s3xySefwMHBAe3bt0e/fv1eeTaXiIiIiN5OWguzWVlZiI6Ohre39/+K0dGBt7c3oqKi8l2mWbNmiI6OlsLrlStXsH37dnTq1KnA9WRmZiItLU3jQURERERvB619A1hKSgpycnJgZWWl0W5lZYULFy7ku8wnn3yClJQUfPDBBxBC4NmzZ/jyyy8LnWYQHByMCRMmlGrtRERERFQ+yOrrbCMjIzF16lQsWLAAHh4euHz5MkaNGoVJkybh559/zneZwMBABAQESM/T0tJgZ2dXViUTERG9OyKCy3Z9rQPfyLAKhQKbNm1C9+7d38j4r8PBwQGjR4/G6NGjtV1KuaG1aQaVK1eGrq4ukpOTNdqTk5NhbW2d7zI///wzBgwYgCFDhqBhw4bo0aMHpk6diuDgYKjV6nyXUalUMDU11XgQERHRuykpKQkjRoxAjRo1oFKpYGdnhy5duiA8PFzbpb0RoaGhMDc3L/P1Dho0qMz+M6C1MKtUKuHq6qqx86jVaoSHh8PT0zPfZR4/fgwdHc2SdXV1AQBCiDdXLBEREcnetWvX4Orqir1792L69Ok4c+YMduzYgdatW8Pf31/b5VEJafVuBgEBAVi6dClWrVqF8+fPY9iwYcjIyICfnx8AYODAgQgM/N+fELp06YKFCxdi3bp1uHr1Knbv3o2ff/4ZXbp0kUItERERUX6++uorKBQKHDt2DL169ULt2rXRoEEDBAQE4MiRIwUud+PGDfTp0wfm5uawsLBAt27dcO3aNen148ePo127dqhcuTLMzMzg5eWFmJgYjTEUCgWWLVuGHj16wNDQELVq1cI///xTaL137txBly5dYGBgAEdHR/zxxx95+syaNQsNGzaEkZER7Ozs8NVXXyE9PR3A8+mZfn5+SE1NhUKhgEKhwPjx4wEAq1evhpubG0xMTGBtbY1PPvlE49aoDx48QP/+/WFpaQkDAwPUqlULK1euLNJnMn78eKxatQpbtmyR1hsZGVnoe30dWg2zffv2xYwZMzBu3Di4uLggNjYWO3bskC4KS0hIQGJiotT/p59+wjfffIOffvoJ9evXx+DBg+Hj44PFixdr6y0QERGRDNy/fx87duyAv78/jIyM8rxe0J/is7Oz4ePjAxMTExw4cACHDh2CsbExOnTogKysLADAo0eP4Ovri4MHD+LIkSOoVasWOnXqhEePHmmMNWHCBPTp0wenT59Gp06d0L9/f9y/f7/AmgcNGoQbN24gIiIC69evx4IFC/Lci19HRwdz5szBuXPnsGrVKuzduxffffcdgOd3gQoJCYGpqSkSExORmJiIMWPGSO9r0qRJOHXqFDZv3oxr165h0KBB0rg///wz/vvvP/z77784f/48Fi5ciMqVKxfpMxkzZgz69OmDDh06SOtt1qxZ4RvoNWj9ArDhw4dj+PDh+b72coqvUKECgoKCEBQUVAaVERER0dvi8uXLEEKgbt26xVouLCwMarUay5Ytg0KhAACsXLkS5ubmiIyMRPv27dGmTRuNZZYsWQJzc3Ps27cPH374odQ+aNAg9OvXDwAwdepUzJkzB8eOHUOHDh3yrPfixYv4999/cezYMbz//vsAgOXLl6NevXoa/V68EMzBwQGTJ0/Gl19+iQULFkCpVMLMzAwKhSLP9UifffaZ9HONGjUwZ84cvP/++0hPT4exsTESEhLQuHFjuLm5SWMX5zMxMDBAZmZmgddBlSat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ZGRkSHc3GDhwIAIDA6X+w4YNw/379zFq1ChcvHgR27Ztw9SpU+Hv76+tt0BEREREWqTVW3P17dsXd+/exbhx45CUlAQXFxfs2LFDuigsISEBOjr/y9t2dnbYuXMnvv76azRq1Ai2trYYNWoUxo4dq623QERERERapBBCCG0XUZbS0tJgZmaG1NRUmJqalsk6o5aPKfUxPQfPKPUxiYiIiMqD4uQ1Wd3NgIiIiIjoRQyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFsMs0REREQkWwyzRERERCRbDLNEREREJFvlIszOnz8fDg4O0NfXh4eHB44dO1Zg39DQUCgUCo2Hvr5+GVZLREREROWF1sNsWFgYAgICEBQUhJiYGDg7O8PHxwd37twpcBlTU1MkJiZKj+vXr5dhxURERERUXmg9zM6aNQtDhw6Fn58f6tevj0WLFsHQ0BArVqwocBmFQgFra2vpYWVlVYYVExEREVF5odUwm5WVhejoaHh7e0ttOjo68Pb2RlRUVIHLpaenw97eHnZ2dujWrRvOnTtXYN/MzEykpaVpPIiIiIjo7aDVMJuSkoKcnJw8Z1atrKyQlJSU7zJ16tTBihUrsGXLFqxZswZqtRrNmjXDzZs38+0fHBwMMzMz6WFnZ1fq74OIiIiItEPr0wyKy9PTEwMHDoSLiwu8vLywceNGWFpaYvHixfn2DwwMRGpqqvS4ceNGGVdMRERERG9KBW2uvHLlytDV1UVycrJGe3JyMqytrYs0hp6eHho3bozLly/n+7pKpYJKpXrtWomIiIio/NHqmVmlUglXV1eEh4dLbWq1GuHh4fD09CzSGDk5OThz5gyqVq36psokIiIionKqRGH2ypUrpVZAQEAAli5dilWrVuH8+fMYNmwYMjIy4OfnBwAYOHAgAgMDpf4TJ07Erl27cOXKFcTExODTTz/F9evXMWTIkFKriYiIiIjkoUTTDJycnODl5YXBgwejd+/er/WlBX379sXdu3cxbtw4JCUlwcXFBTt27JAuCktISICOzv8y94MHDzB06FAkJSWhYsWKcHV1xeHDh1G/fv0S10BERERE8qQQQojiLhQbG4uVK1fizz//RFZWFvr27YvBgwfD3d39TdRYqtLS0mBmZobU1FSYmpqWyTqjlo8p9TE9B88o9TGJiIiIyoPi5LUSTTNwcXHB7Nmzcfv2baxYsQKJiYn44IMP8N5772HWrFm4e/duiQonIiIiIiqO17oArEKFCujZsyf+/vtvTJs2DZcvX8aYMWNgZ2eHgQMHIjExsbTqJCIiIiLK47XC7IkTJ/DVV1+hatWqmDVrFsaMGYP4+Hjs3r0bt2/fRrdu3UqrTiIiIiKiPEp0AdisWbOwcuVKxMXFoVOnTvj999/RqVMn6UItR0dHhIaGwsHBoTRrJSIiIiLSUKIwu3DhQnz22WcYNGhQgfd3rVKlCpYvX/5axRERERERFaZEYXb37t2oXr26xi2zAEAIgRs3bqB69epQKpXw9fUtlSKJiIiIiPJTojmzNWvWREpKSp72+/fvw9HR8bWLIiIiIiIqihKF2YJuTZuenv5aX6BARERERFQcxZpmEBAQAABQKBQYN24cDA0NpddycnJw9OhRuLi4lGqBREREREQFKVaYPXnyJIDnZ2bPnDkDpVIpvaZUKuHs7IwxY0r/266IiIiIiPJTrDAbEREBAPDz88Ps2bPL7OtgiYiIiIjyU6K7GaxcubK06yAiIiIiKrYih9mePXsiNDQUpqam6NmzZ6F9N27c+NqFERERERG9SpHDrJmZGRQKhfQzEREREZG2FTnMvji1gNMMiIiIiKg8KNF9Zp88eYLHjx9Lz69fv46QkBDs2rWr1AojIiIiInqVEoXZbt264ffffwcAPHz4EO7u7pg5cya6deuGhQsXlmqBREREREQFKVGYjYmJQYsWLQAA69evh7W1Na5fv47ff/8dc+bMKdUCiYiIiIgKUqIw+/jxY5iYmAAAdu3ahZ49e0JHRwdNmzbF9evXS7VAIiIiIqKClCjMOjk5YfPmzbhx4wZ27tyJ9u3bAwDu3LnDL1IgIiIiojJTojA7btw4jBkzBg4ODvDw8ICnpyeA52dpGzduXKoFEhEREREVpETfANa7d2988MEHSExMhLOzs9Tetm1b9OjRo9SKIyIiIiIqTInCLABYW1vD2tpao83d3f21CyIiIiIiKqoShdmMjAz88ssvCA8Px507d6BWqzVev3LlSqkUR0RERERUmBKF2SFDhmDfvn0YMGAAqlatKn3NLRERERFRWSpRmP3333+xbds2NG/evLTrISIiIiIqshLdzaBixYqwsLAo7VqIiIiIiIqlRGF20qRJGDduHB4/flza9RARERERFVmJphnMnDkT8fHxsLKygoODA/T09DRej4mJKZXiiIiIiIgKU6Iw271791Iug4iIiIio+EoUZoOCgkq7DiIiIiKiYivRnFkAePjwIZYtW4bAwEDcv38fwPPpBbdu3Sq14oiIiIiIClOiM7OnT5+Gt7c3zMzMcO3aNQwdOhQWFhbYuHEjEhIS8Pvvv5d2nUREREREeZTozGxAQAAGDRqES5cuQV9fX2rv1KkT9u/fX+zx5s+fDwcHB+jr68PDwwPHjh0r0nLr1q2DQqHgHF4iIiKid1SJwuzx48fxxRdf5Gm3tbVFUlJSscYKCwtDQEAAgoKCEBMTA2dnZ/j4+ODOnTuFLnft2jWMGTMGLVq0KNb6iIiIiOjtUaIwq1KpkJaWlqf94sWLsLS0LNZYs2bNwtChQ+Hn54f69etj0aJFMDQ0xIoVKwpcJicnB/3798eECRNQo0aNYtdPRERERG+HEoXZrl27YuLEicjOzgYAKBQKJCQkYOzYsejVq1eRx8nKykJ0dDS8vb3/V5CODry9vREVFVXgchMnTkSVKlUwePDgV64jMzMTaWlpGg8iIiIiejuUKMzOnDkT6enpsLS0xJMnT+Dl5QUnJyeYmJhgypQpRR4nJSUFOTk5sLKy0mi3srIqcLrCwYMHsXz5cixdurRI6wgODoaZmZn0sLOzK3J9RERERFS+lehuBmZmZti9ezcOHTqEU6dOIT09HU2aNNE4w/omPHr0CAMGDMDSpUtRuXLlIi0TGBiIgIAA6XlaWhoDLREREdFbothhVq1WIzQ0FBs3bsS1a9egUCjg6OgIa2trCCGgUCiKPFblypWhq6uL5ORkjfbk5GRYW1vn6R8fH49r166hS5cuGvUAQIUKFRAXF4eaNWtqLKNSqaBSqYrzFomIiIhIJoo1zUAIga5du2LIkCG4desWGjZsiAYNGuD69esYNGgQevToUayVK5VKuLq6Ijw8XGpTq9UIDw+Hp6dnnv5169bFmTNnEBsbKz26du2K1q1bIzY2lmdciYiIiN4xxTozGxoaiv379yM8PBytW7fWeG3v3r3o3r07fv/9dwwcOLDIYwYEBMDX1xdubm5wd3dHSEgIMjIy4OfnBwAYOHAgbG1tERwcDH19fbz33nsay5ubmwNAnnYiIiIievsVK8z++eef+OGHH/IEWQBo06YNvv/+e/zxxx/FCrN9+/bF3bt3MW7cOCQlJcHFxQU7duyQLgpLSEiAjk6Jv3WXiIiIiN5iCiGEKGpna2tr7NixAy4uLvm+fvLkSXTs2LHYX5xQltLS0mBmZobU1FSYmpqWyTqjlo8p9TE9B88o9TGJiIiIyoPi5LVinfK8f/9+nttovcjKygoPHjwozpBERERERCVWrDCbk5ODChUKnpmgq6uLZ8+evXZRRERERERFUaw5s0IIDBo0qMBbXWVmZpZKUURERERERVGsMOvr6/vKPsW5+IuIiIiI6HUUK8yuXLnyTdVBRERERFRsvOcVEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREckWwywRERERyRbDLBERERHJFsMsEREREclWuQiz8+fPh4ODA/T19eHh4YFjx44V2Hfjxo1wc3ODubk5jIyM4OLigtWrV5dhtURERERUXmg9zIaFhSEgIABBQUGIiYmBs7MzfHx8cOfOnXz7W1hY4Mcff0RUVBROnz4NPz8/+Pn5YefOnWVcORERERFpm9bD7KxZszB06FD4+fmhfv36WLRoEQwNDbFixYp8+7dq1Qo9evRAvXr1ULNmTYwaNQqNGjXCwYMHy7hyIiIiItI2rYbZrKwsREdHw9vbW2rT0dGBt7c3oqKiXrm8EALh4eGIi4tDy5Yt8+2TmZmJtLQ0jQcRERERvR20GmZTUlKQk5MDKysrjXYrKyskJSUVuFxqaiqMjY2hVCrRuXNnzJ07F+3atcu3b3BwMMzMzKSHnZ1dqb4HIiIiItIerU8zKAkTExPExsbi+PHjmDJlCgICAhAZGZlv38DAQKSmpkqPGzdulG2xRERERPTGVNDmyitXrgxdXV0kJydrtCcnJ8Pa2rrA5XR0dODk5AQAcHFxwfnz5xEcHIxWrVrl6atSqaBSqUq1biIiIiIqH7R6ZlapVMLV1RXh4eFSm1qtRnh4ODw9PYs8jlqtRmZm5psokYiIiIjKMa2emQWAgIAA+Pr6ws3NDe7u7ggJCUFGRgb8/PwAAAMHDoStrS2Cg4MBPJ8D6+bmhpo1ayIzMxPbt2/H6tWrsXDhQm2+DSIiIiLSAq2H2b59++Lu3bsYN24ckpKS4OLigh07dkgXhSUkJEBH538nkDMyMvDVV1/h5s2bMDAwQN26dbFmzRr07dtXW2+BiIiIiLREIYQQ2i6iLKWlpcHMzAypqakwNTUtk3VGLR9T6mN6Dp5R6mMSERERlQfFyWuyvJsBERERERHAMEtEREREMsYwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyVS7C7Pz58+Hg4AB9fX14eHjg2LFjBfZdunQpWrRogYoVK6JixYrw9vYutD8RERERvb20HmbDwsIQEBCAoKAgxMTEwNnZGT4+Prhz506+/SMjI9GvXz9EREQgKioKdnZ2aN++PW7dulXGlRMRERGRtimEEEKbBXh4eOD999/HvHnzAABqtRp2dnYYMWIEvv/++1cun5OTg4oVK2LevHkYOHDgK/unpaXBzMwMqampMDU1fe36iyJq+ZhSH9Nz8IxSH5OIiIioPChOXtPqmdmsrCxER0fD29tbatPR0YG3tzeioqKKNMbjx4+RnZ0NCwuLfF/PzMxEWlqaxoOIiIiI3g5aDbMpKSnIycmBlZWVRruVlRWSkpKKNMbYsWNhY2OjEYhfFBwcDDMzM+lhZ2f32nUTERERUfmg9Tmzr+OXX37BunXrsGnTJujr6+fbJzAwEKmpqdLjxo0bZVwlEREREb0pFbS58sqVK0NXVxfJycka7cnJybC2ti502RkzZuCXX37Bnj170KhRowL7qVQqqFSqUqmXiIiIiMoXrZ6ZVSqVcHV1RXh4uNSmVqsRHh4OT0/PApf79ddfMWnSJOzYsQNubm5lUSoRERERlUNaPTMLAAEBAfD19YWbmxvc3d0REhKCjIwM+Pn5AQAGDhwIW1tbBAcHAwCmTZuGcePGYe3atXBwcJDm1hobG8PY2Fhr74OIiIiIyp7Ww2zfvn1x9+5djBs3DklJSXBxccGOHTuki8ISEhKgo/O/E8gLFy5EVlYWevfurTFOUFAQxo8fX5alExEREZGWaf0+s2WN95klIiIiKt9kc59ZIiIiIqLXwTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREssUwS0RERESyxTBLRERERLLFMEtEREREsqX1MDt//nw4ODhAX18fHh4eOHbsWIF9z507h169esHBwQEKhQIhISFlVygRERERlTtaDbNhYWEICAhAUFAQYmJi4OzsDB8fH9y5cyff/o8fP0aNGjXwyy+/wNrauoyrJSIiIqLyRqthdtasWRg6dCj8/PxQv359LFq0CIaGhlixYkW+/d9//31Mnz4dH3/8MVQqVRlXS0RERETljdbCbFZWFqKjo+Ht7f2/YnR04O3tjaioqFJbT2ZmJtLS0jQeRERERPR20FqYTUlJQU5ODqysrDTarayskJSUVGrrCQ4OhpmZmfSws7MrtbGJiIiISLu0fgHYmxYYGIjU1FTpcePGDW2XRERERESl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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# ============================================================\n", "# BUILD GAP DATASETS\n", "# ============================================================\n", "\n", "def build_gap_df(stocks_df, flows_df):\n", "\n", " keys = [\n", " \"Registrar Account - ID\",\n", " \"Product - Isin\",\n", " \"Centralisation Date\"\n", " ]\n", "\n", " df = stocks_df.merge(flows_df, on=keys, how=\"left\")\n", "\n", " df[\"Quantity - NetFlows\"] = df[\"Quantity - NetFlows\"].fillna(0)\n", "\n", " df = df.sort_values(keys)\n", "\n", " df[\"prev_aum\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - AUM\"]\n", " .shift(1)\n", " )\n", "\n", " df[\"flow_lag\"] = (\n", " df.groupby([\"Registrar Account - ID\",\"Product - Isin\"])\n", " [\"Quantity - NetFlows\"]\n", " .shift(1)\n", " .fillna(0)\n", " )\n", "\n", " df[\"expected_aum\"] = df[\"prev_aum\"] + df[\"flow_lag\"]\n", "\n", " df[\"gap\"] = df[\"Quantity - AUM\"] - df[\"expected_aum\"]\n", "\n", " return df\n", "\n", "\n", "df_raw = build_gap_df(stocks, flows)\n", "df_clean = build_gap_df(stocks_repaired, flows)\n", "\n", "\n", "# ============================================================\n", "# COMPUTE GAP PERSISTENCE LENGTH\n", "# ============================================================\n", "\n", "import numpy as np\n", "\n", "def compute_gap_sequences(df, tol=1e-6):\n", "\n", " lengths = []\n", "\n", " for (_, _), g in df.groupby([\"Registrar Account - ID\",\"Product - Isin\"]):\n", "\n", " gaps = g[\"gap\"].values\n", "\n", " current_len = 1\n", "\n", " for i in range(1, len(gaps)):\n", "\n", " if np.isfinite(gaps[i]) and np.isfinite(gaps[i-1]) and abs(gaps[i] - gaps[i-1]) < tol:\n", "\n", " current_len += 1\n", "\n", " else:\n", "\n", " lengths.append(current_len)\n", " current_len = 1\n", "\n", " lengths.append(current_len)\n", "\n", " return np.array(lengths)\n", "\n", "\n", "raw_lengths = compute_gap_sequences(df_raw)\n", "clean_lengths = compute_gap_sequences(df_clean)\n", "\n", "\n", "# ============================================================\n", "# PLOT DISTRIBUTION\n", "# ============================================================\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "plt.figure(figsize=(8,5))\n", "\n", "bins = np.arange(1,30)\n", "\n", "plt.hist(\n", " raw_lengths,\n", " bins=bins,\n", " alpha=0.5,\n", " label=\"Raw dataset\",\n", " density=True\n", ")\n", "\n", "plt.hist(\n", " clean_lengths,\n", " bins=bins,\n", " alpha=0.5,\n", " label=\"Clean dataset\",\n", " density=True\n", ")\n", "\n", "plt.xlabel(\"Gap persistence length (number of periods)\")\n", "plt.ylabel(\"Density\")\n", "\n", "plt.title(\"Distribution of persistent accounting gaps\")\n", "\n", "plt.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 63, "id": "b7dfc5a3-e2b0-4fbf-894a-a66226f38165", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "max_len = 20\n", "\n", "# clip lengths\n", "raw_clipped = np.minimum(raw_lengths, max_len)\n", "clean_clipped = np.minimum(clean_lengths, max_len)\n", "\n", "# compute histogram\n", "raw_hist = np.bincount(raw_clipped, minlength=max_len+1)\n", "clean_hist = np.bincount(clean_clipped, minlength=max_len+1)\n", "\n", "# normalize\n", "raw_hist = raw_hist / raw_hist.sum()\n", "clean_hist = clean_hist / clean_hist.sum()\n", "\n", "x = np.arange(max_len+1)\n", "\n", "plt.figure(figsize=(8,4))\n", "\n", "plt.plot(x, raw_hist, marker=\"o\", label=\"Raw dataset\")\n", "plt.plot(x, clean_hist, marker=\"o\", label=\"Clean dataset\")\n", "\n", "plt.xlabel(\"Gap persistence length (20 = 20+)\")\n", "plt.ylabel(\"Share of sequences\")\n", "\n", "plt.title(\"Persistence of accounting gaps\")\n", "\n", "plt.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "5a9d6cd8-e92b-4975-969d-cb3d6d9e4bce", "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.13.11" } }, "nbformat": 4, "nbformat_minor": 5 }