{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Which legal entity sits behind the bank?\n", "Match the registration authority and bank certificate, then link the bank\u2019s LEI to its reported ultimate parent.\n", "FDIC certificate 628 matches GLEIF authority RA000744 and registration identifier 628. The bank and its ultimate parent have different LEIs. Current records captured 2026-10-31 UTC; this is not a historical ownership graph." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"Match the registration authority and bank certificate, then link the bank\u2019s LEI to its reported ultimate parent.\n", "\n", "Research example. Live observations can change.\n", "\"\"\"\n", "\n", "from datetime import UTC, datetime\n", "from textwrap import fill\n", "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "\n", "AS_OF = datetime.now(UTC).date()\n", "NAVY, BLUE, PINK = \"#132244\", \"#8fb9ff\", \"#e7a2cc\"\n", "\n", "\n", "def fetch(qj):\n", " raw = {\n", " \"fdic_bank\": qj.domains.call(\n", " \"regulatory.banking.get_institutions\", provider=\"fdic\", filters=\"CERT:628\", limit=1\n", " ),\n", " \"lei_search\": qj.domains.call(\n", " \"reference.entities.search_lei_records\",\n", " provider=\"gleif\",\n", " query=\"JPMorgan Chase Bank\",\n", " country=\"US\",\n", " entity_status=\"ACTIVE\",\n", " registration_status=\"ISSUED\",\n", " page_size=10,\n", " ),\n", " }\n", " _, record = linked_bank(raw)\n", " raw[\"lei_parent\"] = qj.domains.call(\"reference.entities.get_ultimate_parent\", provider=\"gleif\", lei=record[\"id\"])\n", " return raw\n", "\n", "\n", "def analyze(raw):\n", " from textwrap import fill\n", "\n", " from matplotlib.patches import FancyBboxPatch\n", "\n", " bank, record = linked_bank(raw)\n", " parent = payload(raw[\"lei_parent\"])\n", " if not isinstance(parent, dict) or \"attributes\" not in parent:\n", " raise ValueError(\"No reported ultimate-parent record\")\n", " entity = record[\"attributes\"][\"entity\"]\n", " parent_entity = parent[\"attributes\"][\"entity\"]\n", " if parent[\"id\"] == record[\"id\"]:\n", " raise ValueError(\"The returned parent is the same legal entity\")\n", " rows = [\n", " {\n", " \"Role\": \"Regulated bank\",\n", " \"Identifier\": \"FDIC \" + str(int(bank[\"CERT\"])),\n", " \"Name\": entity[\"legalName\"][\"name\"],\n", " \"Status\": \"ACTIVE\" if bank[\"ACTIVE\"] == 1 else \"INACTIVE\",\n", " },\n", " {\n", " \"Role\": \"Bank legal entity\",\n", " \"Identifier\": record[\"id\"],\n", " \"Name\": entity[\"legalName\"][\"name\"],\n", " \"Status\": record[\"attributes\"][\"registration\"][\"status\"],\n", " },\n", " {\n", " \"Role\": \"Ultimate parent\",\n", " \"Identifier\": parent[\"id\"],\n", " \"Name\": parent_entity[\"legalName\"][\"name\"],\n", " \"Status\": parent[\"attributes\"][\"registration\"][\"status\"],\n", " },\n", " ]\n", " result = pd.DataFrame(rows)\n", " fig, (ax,) = canvas(\"Entity identity \u00b7 bank and ultimate parent\", \"\")\n", " ax.set_axis_off()\n", " for index, row in enumerate(rows):\n", " y = 0.74 - index * 0.31\n", " box = FancyBboxPatch(\n", " (0.10, y),\n", " 0.80,\n", " 0.22,\n", " boxstyle=\"round,pad=.015,rounding_size=.015\",\n", " transform=ax.transAxes,\n", " facecolor=\"#1c3156\",\n", " edgecolor=BLUE if index < 2 else PINK,\n", " linewidth=1.3,\n", " )\n", " ax.add_patch(box)\n", " ax.text(0.14, y + 0.175, row[\"Role\"], transform=ax.transAxes, color=PINK if index == 2 else BLUE, fontsize=10)\n", " ax.text(0.14, y + 0.115, fill(row[\"Name\"], width=45), transform=ax.transAxes, fontsize=12, va=\"center\")\n", " ax.text(\n", " 0.14,\n", " y + 0.035,\n", " row[\"Identifier\"] + \" \u00b7 \" + row[\"Status\"],\n", " transform=ax.transAxes,\n", " fontsize=9,\n", " color=\"#afc1dc\",\n", " )\n", " if index < 2:\n", " ax.annotate(\n", " \"\",\n", " xy=(0.5, y - 0.075),\n", " xytext=(0.5, y - 0.02),\n", " xycoords=\"axes fraction\",\n", " arrowprops={\"arrowstyle\": \"->\", \"color\": \"#afc1dc\", \"lw\": 1.3},\n", " )\n", " return result, fig\n", "\n", "\n", "def payload(response):\n", " if hasattr(response, \"to_dict\"):\n", " response = response.to_dict()\n", " while isinstance(response, dict):\n", " if response.get(\"error\") or response.get(\"error_code\") or response.get(\"success\") is False:\n", " raise ValueError(\"Provider request failed\")\n", " if \"data\" in response:\n", " response = response[\"data\"]\n", " elif \"value\" in response:\n", " response = response[\"value\"]\n", " else:\n", " break\n", " return response\n", "\n", "\n", "def frame(response, date=\"date\"):\n", " rows = payload(response)\n", " if not isinstance(rows, list) or not rows:\n", " raise ValueError(\"No provider observations returned\")\n", " result = pd.DataFrame(rows)\n", " if date:\n", " result[date] = pd.to_datetime(result[date], utc=True)\n", " result = result.sort_values(date)\n", " return result\n", "\n", "\n", "def canvas(title, ylabel, panels=1):\n", " plt.rcParams.update(\n", " {\n", " \"figure.facecolor\": NAVY,\n", " \"axes.facecolor\": NAVY,\n", " \"savefig.facecolor\": NAVY,\n", " \"text.color\": \"#e9eef8\",\n", " \"axes.labelcolor\": \"#afc1dc\",\n", " \"xtick.color\": \"#afc1dc\",\n", " \"ytick.color\": \"#afc1dc\",\n", " \"axes.edgecolor\": \"#34486b\",\n", " \"grid.color\": \"#34486b\",\n", " \"font.size\": 10,\n", " \"axes.prop_cycle\": plt.cycler(color=[BLUE, PINK, \"#ba9dea\"]),\n", " }\n", " )\n", " fig, axes = plt.subplots(panels, 1, figsize=(9, 6.1), squeeze=False, layout=\"constrained\")\n", " fig.suptitle(fill(title, width=58), fontsize=17, x=0.04, ha=\"left\", weight=\"medium\")\n", " for ax in axes.flat:\n", " ax.grid(axis=\"y\", alpha=0.45)\n", " ax.set_axisbelow(True)\n", " ax.spines[[\"top\", \"right\"]].set_visible(False)\n", " axes.flat[0].set_ylabel(ylabel)\n", " return fig, list(axes.flat)\n", "\n", "\n", "def run():\n", " from quantjourney.sdk import QuantJourneyAPI\n", "\n", " qj = QuantJourneyAPI.from_env()\n", " raw = fetch(qj)\n", " table, fig = analyze(raw)\n", " print(table.to_string(index=False))\n", " fig.savefig(OUTPUT, dpi=180)\n", " plt.show()\n", "\n", "\n", "def linked_bank(raw):\n", " bank = frame(raw[\"fdic_bank\"], date=None).iloc[0]\n", " certificate = str(int(bank[\"CERT\"]))\n", " matches = []\n", " for record in payload(raw[\"lei_search\"]):\n", " entity = record[\"attributes\"][\"entity\"]\n", " if entity.get(\"registeredAt\", {}).get(\"id\") == \"RA000744\" and str(entity.get(\"registeredAs\")) == certificate:\n", " matches.append(record)\n", " if len(matches) != 1:\n", " raise ValueError(\"Need one LEI with an exact FDIC authority and certificate match\")\n", " return bank, matches[0]\n", "\n", "\n", "OUTPUT = \"sdk_entity_identity.png\"\n", "\n", "if __name__ == \"__main__\":\n", " run()\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }