<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Feng Yu</title>
    <description>The latest articles on DEV Community by Feng Yu (@fengyugbt).</description>
    <link>https://dev.to/fengyugbt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4113833%2Ffa0629ad-e957-46e6-8316-f702368a55dc.jpg</url>
      <title>DEV Community: Feng Yu</title>
      <link>https://dev.to/fengyugbt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fengyugbt"/>
    <language>en</language>
    <item>
      <title>Build an Odoo MCP Server: Let AI Agents Query Your ERP (Sales, Inventory, Customers)</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Wed, 07 Oct 2026 08:33:04 +0000</pubDate>
      <link>https://dev.to/fengyugbt/build-an-odoo-mcp-server-let-ai-agents-query-your-erp-sales-inventory-customers-5b50</link>
      <guid>https://dev.to/fengyugbt/build-an-odoo-mcp-server-let-ai-agents-query-your-erp-sales-inventory-customers-5b50</guid>
      <description>&lt;p&gt;description: "A practical, ~200-line guide to building a read-only MCP server that connects Claude Desktop, Cursor and Windsurf to Odoo ERP via XML-RPC — with working Python code, Docker deployment and Claude Desktop config."&lt;/p&gt;

&lt;p&gt;Most public MCP servers are generic — file readers, web scrapers, git helpers. Useful for developers, useless for business teams.&lt;/p&gt;

&lt;p&gt;If you run an ERP, your business data — sales orders, inventory, customers — lives behind XML-RPC APIs that no AI agent can reach out of the box. Odoo is the most popular open-source ERP, and its users keep answering the same questions over and over: &lt;em&gt;"What's the stock?", "Which orders are unpaid?", "Who are our top customers?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This post shows how to build a &lt;strong&gt;read-only MCP server&lt;/strong&gt; that connects Claude Desktop / Cursor / Windsurf to Odoo in about 200 lines of Python. No vendor lock-in, no custom dashboards, no spreadsheet exports.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MCP in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol (MCP) is an open standard (introduced by Anthropic in late 2024) that lets AI assistants call external tools in a uniform way. A server exposes &lt;strong&gt;tools&lt;/strong&gt;; clients like Claude Desktop or Cursor discover them and let the model invoke them through natural language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Client (Claude Desktop / Cursor / Windsurf)
        │  MCP over stdio
        ▼
Odoo MCP Server  (Python + MCP SDK)
        │  XML-RPC
        ▼
Odoo ERP  (sale.order · stock.quant · res.partner · product.product)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;odoo-mcp/
├── server.py          # MCP tools definition
├── odoo_client.py     # thin Odoo XML-RPC wrapper
├── requirements.txt
├── Dockerfile
└── .env               # credentials — never commit this
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mcp&amp;gt;=2.0
python-dotenv&amp;gt;=1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Odoo client — &lt;code&gt;odoo_client.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A thin wrapper around Odoo's XML-RPC endpoint. Two details matter in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API keys&lt;/strong&gt;: use an Odoo API key (Settings → Users → your profile → API Keys, available since Odoo 17) instead of a plain password.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JSON-safe results&lt;/strong&gt;: Odoo returns &lt;code&gt;datetime&lt;/code&gt; and &lt;code&gt;tuple&lt;/code&gt; objects that Python's &lt;code&gt;json&lt;/code&gt; cannot serialize directly. MCPServer serializes tool results to JSON, so we normalize everything before returning.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Minimal Odoo XML-RPC client with JSON-safe result normalization.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;xmlrpc.client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_to_json_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;_to_json_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;_to_json_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;_to_json_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OdooClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/xmlrpc/2/object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_uid&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xmlrpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServerProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/xmlrpc/2/common&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Odoo authentication failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_uid&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_kw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;_to_json_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. MCP server — &lt;code&gt;server.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We expose six read-only tools covering the questions Odoo users ask most. &lt;strong&gt;No write tools in the MVP&lt;/strong&gt; — read-only keeps the attack surface small and makes the open-source version safe to run against a production database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Odoo MCP server — read-only tools for AI agents.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.mcpserver&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MCPServer&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;odoo_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OdooClient&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MCPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;odoo-mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OdooClient&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OdooClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OdooClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ODOO_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ODOO_DB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ODOO_USER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ODOO_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_odoo_models&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;List available Odoo business models, e.g. sale.order, stock.quant, res.partner.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ir.model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_partners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search customers and suppliers by name or email.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ilike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ilike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;res.partner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_sales_orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search sales orders by state (draft, sent, sale, done) or customer name.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ilike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search sellable products by name or internal reference (SKU).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale_ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ilike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product.product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_stock_quant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Query real-time on-hand stock by product SKU or name.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;product_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;product_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;product_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ilike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product.product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;quants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stock.quant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location_id.usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reserved_quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;quants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;quants&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_sale_order_detail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get a full sales order by its name, including line items and totals.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_name&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;search_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale.order.line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_uom_qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_subtotal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Configuration — &lt;code&gt;.env&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ODOO_URL=https://your-odoo-instance.odoo.com
ODOO_DB=your-db-name
ODOO_USER=your-login@example.com
ODOO_API_KEY=your-api-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Create the API key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;in Odoo: Settings → Users → your profile → API Keys. For production, create a &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dedicated read-only user&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and grant access only to the models you intend to expose.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Run locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate        &lt;span class="c"&gt;# Windows: .venv\Scripts\activate&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server now listens on &lt;strong&gt;stdio&lt;/strong&gt;. Point your MCP client at it.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Docker deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "server.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; odoo-mcp &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env odoo-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Connect Claude Desktop
&lt;/h3&gt;

&lt;p&gt;Add the server to &lt;code&gt;claude_desktop_config.json&lt;/code&gt; (Claude → Settings → Developer → Edit Config):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"odoo-mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--rm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--env-file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/absolute/path/to/.env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"odoo-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Claude Desktop and try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Show all confirmed sales orders this month, grouped by customer."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"How much stock of SKU PROD-001 is available in warehouse WH01?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Find customers from Germany with unpaid orders."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Show me the full detail of order SO00123, including line items."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read-only by default.&lt;/strong&gt; The MVP exposes no write tools. Write-back (create quotation, create partner, update notes) belongs in a paid tier backed by a full audit log.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Least privilege.&lt;/strong&gt; Create a dedicated Odoo user with read access only to the models you expose. Do not reuse an admin account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model whitelist (recommended).&lt;/strong&gt; Add an &lt;code&gt;ODOO_ALLOWED_MODELS&lt;/code&gt; env var and filter in &lt;code&gt;client()&lt;/code&gt; to block sensitive models like &lt;code&gt;hr.employee&lt;/code&gt; or &lt;code&gt;account.move.line&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never commit &lt;/strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; Add it to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;[x] Read-only queries (MVP)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;[ ] Write tools: create quotation / partner, with audit logging&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;[ ] Multi-instance support (multiple Odoo databases)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;[ ] Model whitelist configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;[ ] Managed hosting for non-technical teams&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters for Odoo teams
&lt;/h2&gt;

&lt;p&gt;Odoo consultants, small manufacturers and trading companies constantly answer the same questions: &lt;em&gt;"What's the stock?", "Which orders are unpaid?", "Who are our top customers?"&lt;/em&gt; An MCP server turns those into a chat conversation while keeping the data live — no exports, no spreadsheets, no custom dashboards.&lt;/p&gt;

&lt;p&gt;It is also a practical business opportunity: generic MCP servers are hard to monetize (they are commoditized), but &lt;strong&gt;vertical ERP connectors have clear value&lt;/strong&gt; because they remove custom scripting work for real customers. The read-only MVP is the foundation for paid tiers (write access, audit logs, hosting) and custom integrations (manufacturing BOM, quality, MRP).&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;MCP turns an ERP from a database into a conversation. With roughly 200 lines of Python you get a working read-only Odoo MCP server that works across Claude Desktop, Cursor and Windsurf — and you keep full control of your data and credentials.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of an open-source project — &lt;a href="https://github.com/fengyuGbt/odoo-mcp" rel="noopener noreferrer"&gt;github.com/fengyuGbt/odoo-mcp&lt;/a&gt;. Star the repo, open issues, or contact me for custom Odoo MCP integrations (write-back, audit logs, manufacturing modules).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>odoo</category>
      <category>python</category>
    </item>
    <item>
      <title>I Built a Fully Local AI Agent That Turns Screen Stress Into Outdoor Missions</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Wed, 07 Oct 2026 08:23:00 +0000</pubDate>
      <link>https://dev.to/fengyugbt/i-built-a-fully-local-ai-agent-that-turns-screen-stress-into-outdoor-missions-55b</link>
      <guid>https://dev.to/fengyugbt/i-built-a-fully-local-ai-agent-that-turns-screen-stress-into-outdoor-missions-55b</guid>
      <description>&lt;p&gt;&lt;em&gt;Week 1 of the Hacktoberfest Open-Source AI Challenge asked us to build something that gets people outside. Here's what I shipped: **GrassTrace&lt;/em&gt;* — a local LLM agent that reads your stress signal and generates a concrete 10-minute outdoor mission.*&lt;/p&gt;

&lt;p&gt;If you spend your day in front of markets, dashboards, or a ticket queue, you know the feeling: eyes tired, mind buzzing, and "take a break" never converts into an actual action. Nudges don't work because they're generic. What actually works is a &lt;em&gt;specific&lt;/em&gt; task: go to the street-corner park, find five different colors of flowers, listen for bird calls, feel the wind. Ten minutes. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GrassTrace&lt;/strong&gt; is that agent. It runs 100% locally — open-weight model, no API calls, your data never leaves your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: stress signals you can't act on
&lt;/h2&gt;

&lt;p&gt;A stress signal like &lt;em&gt;"I've been staring at charts for four hours"&lt;/em&gt; is real, but it doesn't tell you what to do. Generic advice ("take a walk!") is exactly what your brain ignores. The fix is to make the advice concrete, sensory, and short enough to not feel like a chore.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GrassTrace does
&lt;/h2&gt;

&lt;p&gt;You describe how you feel — stress type, intensity, how many minutes you have — and a local LLM generates a structured mission:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;diagnosis&lt;/strong&gt; — one sentence on why you need to leave the screen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mission&lt;/strong&gt; — a walkable place, a specific action, and 3 observable details (colors, sounds, textures, scents)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reflection&lt;/strong&gt; — a guided question for when you come back (closes the loop, makes it stick)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example output (Chinese, the model is bilingual):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"diagnosis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"眼睛疲劳可能是因为长时间盯着屏幕，建议休息一下并进行户外活动。"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"在楼下人行道上走动，观察周围环境"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"place"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"楼下人行道"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"observe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"花的颜色"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"树的形状"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"路人的表情"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"路旁的气味"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"路面的温度"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reflection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10分钟的观察活动之后，你是否感觉到眼睛的酸胀有所缓解？"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture: one inference pass, JSON in, JSON out
&lt;/h2&gt;

&lt;p&gt;Three-step prompt pipeline in a single local inference pass:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Diagnose&lt;/strong&gt; — why leaving the screen matters right now&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mission&lt;/strong&gt; — action + walkable place + 3 specific observations + duration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflection&lt;/strong&gt; — a guided question for when you return&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model is constrained to output nothing but a JSON object; invalid outputs are retried with schema validation (max 3 attempts). A deterministic seed keeps output reproducible. The system prompt explicitly forbids market commentary or investment advice — this is not a financial tool, it tells you to go outside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inference&lt;/td&gt;
&lt;td&gt;llama-cpp-python (OpenAI-compatible API, CPU)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Qwen2.5-3B-Instruct GGUF Q4_K_M (open-weight, ~2 GB, bilingual)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Streamlit with a "I'm back" check-in loop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On my reference machine (i7-4790, 16 GB RAM, no GPU) a mission takes ~30 seconds. That's the honest cost of local CPU inference — and the privacy/ownership tradeoff is worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local matters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by default&lt;/strong&gt;: stress is personal. No cloud, no logs, no prompts sent anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero marginal cost&lt;/strong&gt;: the model runs on hardware you already own; you can even disconnect the internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The spirit of the challenge&lt;/strong&gt;: open-weight models + local inference is exactly what this week's challenge asks for.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub: &lt;a href="https://github.com/fengyuGbt/grasstrace" rel="noopener noreferrer"&gt;https://github.com/fengyuGbt/grasstrace&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The README has full setup: download the GGUF from ModelScope (or Hugging Face), &lt;code&gt;pip install llama-cpp-python streamlit&lt;/code&gt;, one command to run, and you have a working agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;English mission cards (the model is bilingual; UI is currently Chinese-first)&lt;/li&gt;
&lt;li&gt;Auto-trigger on market volatility — the stress signal comes to you instead of you typing it&lt;/li&gt;
&lt;li&gt;Voice input and a PWA shell for phone use&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;AI disclosure: this post and the project were built with heavy AI assistance; the author reviewed, tested, and verified everything. The project itself is fully local — the AI that powers it runs on your own machine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>hacktoberfest</category>
      <category>touchgrass</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Credibility Line: Why Rules Survive Only Where Overrides Are Seen</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 06:43:36 +0000</pubDate>
      <link>https://dev.to/fengyugbt/the-credibility-line-why-rules-survive-only-where-overrides-are-seen-4m14</link>
      <guid>https://dev.to/fengyugbt/the-credibility-line-why-rules-survive-only-where-overrides-are-seen-4m14</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 24 of the Fat Tail Notes series — the price of breaking a promise.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook
&lt;/h2&gt;

&lt;p&gt;Part 23 priced the policy loophole and found the cliff: a 10% exemption expectation eats 64% of the backstop's damage. But that model treated the exemption rate &lt;code&gt;p&lt;/code&gt; as something the market just &lt;em&gt;expects&lt;/em&gt;. This part asks the question the series has been walking toward since Part 18: &lt;strong&gt;why is &lt;code&gt;p&lt;/code&gt; small at all? Why does a policymaker not override the rule?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is that exemption is not free. It has an expected cost — the probability that it is seen, times the punishment when it is, times how much the policymaker values the credibility it destroys. Change the price of exemption and you change the exemption rate. &lt;strong&gt;Commitment is not a matter of will. It is a matter of price.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This part makes &lt;code&gt;p&lt;/code&gt; endogenous and finds three things. There is a credibility line — a threshold below which exemption is free and the promise is empty — and institutions move that line. The spiral is self-reinforcing: once credibility dips, exemption cheapens itself. And the vaccine is not punishment but &lt;strong&gt;visibility&lt;/strong&gt;: a severe punishment that never fires protects nothing, because the exemption was never seen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The machine
&lt;/h2&gt;

&lt;p&gt;The policymaker compares the private benefit of exemption &lt;code&gt;b&lt;/code&gt; (political temptation: saving the day, keeping a position, avoiding short-run pain) against its expected cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p* = min(1, b / (v × s × C))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;v&lt;/code&gt; = &lt;strong&gt;visibility&lt;/strong&gt;: the probability that an exemption is seen by the market;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s&lt;/code&gt; = &lt;strong&gt;punishment&lt;/strong&gt;: the credibility loss when it is seen;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;C&lt;/code&gt; = current credibility. The expected cost of one exemption is &lt;code&gt;v·s·C&lt;/code&gt; — &lt;strong&gt;proportional to the credibility it destroys&lt;/strong&gt;. High credibility makes exemption expensive; low credibility makes it nearly free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Effective capacity is then &lt;code&gt;C_eff = C × (1 − p*)&lt;/code&gt;, and the market prices tail losses at &lt;code&gt;C_eff&lt;/code&gt; (the P4/P6 machinery, 2,000 paths per level, seed 20260921).&lt;/p&gt;

&lt;p&gt;Two structural facts fall out immediately, before any numbers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The credibility line.&lt;/strong&gt; &lt;code&gt;C_crit = b/(v·s)&lt;/code&gt;. Below &lt;code&gt;C_crit&lt;/code&gt;, &lt;code&gt;p* = 1&lt;/code&gt;: exemption is free, there is no reason left to keep the promise. The line is the institutional position of Part 23's cliff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The spiral is endogenous.&lt;/strong&gt; &lt;code&gt;p*&lt;/code&gt; rises as &lt;code&gt;C&lt;/code&gt; falls — the cost of exemption falls with the credibility it destroys. A wounded reputation invites more exemptions, which wound it further.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anchor: reputation and institutions are the same price
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Kreps &amp;amp; Wilson (1982).&lt;/strong&gt; The chain-store paradox said a monopolist can never credibly promise to fight entry — backward induction kills it. The paradox dissolved when Kreps and Wilson added a tiny uncertainty about the monopolist's "type": if there is even a small probability that he is irrational enough to fight, then it is &lt;em&gt;rational&lt;/em&gt; for a normal monopolist to fight early to build a reputation — because the reputation has future value. Reputation makes commitments credible under imperfect information, and the mechanism is price: fighting is expensive today and cheapens tomorrow's entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;North (1993 Nobel) and North-Weingast (1989).&lt;/strong&gt; Institutions are devices that make sovereign commitments credible. After the Glorious Revolution, the English crown bound itself — Parliament controlled taxation, the king could not unilaterally renege — and the result was a fall in borrowing costs and the birth of a standing public debt market. The sovereign did not become more honest; the &lt;em&gt;price of reneging&lt;/em&gt; went up. Credibility is an institutional property, not a personal one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rhetorical override.&lt;/strong&gt; Draghi's "whatever it takes" (2012) was followed by OMT — conditional, auditable, never activated. The promise was credible because the override would have been &lt;em&gt;seen&lt;/em&gt;: visibility is what made the price of exemption higher than its benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1 — the credibility line is institutional
&lt;/h2&gt;

&lt;p&gt;Critical threshold &lt;code&gt;C_crit = b/(v·s)&lt;/code&gt;, with &lt;code&gt;b = 0.10&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;v·s&lt;/th&gt;
&lt;th&gt;0.10&lt;/th&gt;
&lt;th&gt;0.25&lt;/th&gt;
&lt;th&gt;0.50&lt;/th&gt;
&lt;th&gt;1.00&lt;/th&gt;
&lt;th&gt;2.00&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C_crit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.05&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The line is set by the institutional environment, not by the policymaker's character. At &lt;code&gt;v·s = 0.10&lt;/code&gt; (opaque environment, weak consequences) the credibility line sits at 1.00 — the &lt;em&gt;entire&lt;/em&gt; credibility space is free-exemption territory. At &lt;code&gt;v·s = 2.00&lt;/code&gt; (transparent, audited, consequential) the line drops to 0.05 — even a nearly-broken reputation still holds its promise. The same policymaker, the same temptation, different institutions: one keeps his word, the other does not. &lt;strong&gt;You do not trust the person. You trust the line.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2 — the spiral is self-reinforcing
&lt;/h2&gt;

&lt;p&gt;Endogenous exemption rate &lt;code&gt;p*&lt;/code&gt; per discipline level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;v·s=0.10&lt;/th&gt;
&lt;th&gt;v·s=0.25&lt;/th&gt;
&lt;th&gt;v·s=0.50&lt;/th&gt;
&lt;th&gt;v·s=1.00&lt;/th&gt;
&lt;th&gt;v·s=2.00&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.53&lt;/td&gt;
&lt;td&gt;0.27&lt;/td&gt;
&lt;td&gt;0.13&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the &lt;code&gt;v·s=0.25&lt;/code&gt; column. At C=1.00 the exemption rate is 0.40. At C=0.50 it is 0.80. At C=0.25 it is 1.00 — the promise is empty. &lt;strong&gt;Exemption cheapens itself&lt;/strong&gt;: every bit of credibility destroyed makes the next exemption cheaper, which destroys more credibility. This is the institutional reason behind Part 23's "cliff in the first mouthful": the first exemption brings C near the credibility line, and below the line the remaining exemptions arrive on their own. There is no halfway healing — once the spiral starts, it runs to the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3 — visibility is the vaccine, punishment is only half of it
&lt;/h2&gt;

&lt;p&gt;Effective capacity &lt;code&gt;C_eff = C × (1 − p*)&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;v·s=0.10&lt;/th&gt;
&lt;th&gt;v·s=0.25&lt;/th&gt;
&lt;th&gt;v·s=0.50&lt;/th&gt;
&lt;th&gt;v·s=1.00&lt;/th&gt;
&lt;th&gt;v·s=2.00&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;0.90&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;td&gt;0.55&lt;/td&gt;
&lt;td&gt;0.65&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;0.45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At &lt;code&gt;v·s = 0.10&lt;/code&gt; — say, &lt;code&gt;v = 0.2&lt;/code&gt; and &lt;code&gt;s = 0.5&lt;/code&gt;: exemptions are rarely seen but heavily punished — the &lt;em&gt;entire&lt;/em&gt; credibility space leaks. Even a flawless reputation (C=1.00) collapses to &lt;code&gt;C_eff = 0&lt;/code&gt;. The punishment never fires, because the exemption is never seen. &lt;strong&gt;Severity protects nothing when visibility is zero.&lt;/strong&gt; The institutional lesson of OMT is not that the ECB was willing to punish itself; it is that any override would have been seen, and seen instantly — conditionality, program audits, market scrutiny. Visibility is the vaccine; punishment is the booster that makes it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the market pays
&lt;/h2&gt;

&lt;p&gt;Bare-market p1 priced at the endogenously-credible capacity:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;v·s=0.10&lt;/th&gt;
&lt;th&gt;v·s=0.25&lt;/th&gt;
&lt;th&gt;v·s=0.50&lt;/th&gt;
&lt;th&gt;v·s=1.00&lt;/th&gt;
&lt;th&gt;v·s=2.00&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−59.3%&lt;/td&gt;
&lt;td&gt;−54.2%&lt;/td&gt;
&lt;td&gt;−51.3%&lt;/td&gt;
&lt;td&gt;−49.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−63.1%&lt;/td&gt;
&lt;td&gt;−60.6%&lt;/td&gt;
&lt;td&gt;−58.4%&lt;/td&gt;
&lt;td&gt;−57.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−53.2%&lt;/td&gt;
&lt;td&gt;−63.4%&lt;/td&gt;
&lt;td&gt;−62.6%&lt;/td&gt;
&lt;td&gt;−62.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−48.5%&lt;/td&gt;
&lt;td&gt;−58.0%&lt;/td&gt;
&lt;td&gt;−62.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two readings. First, the credibility-death world (&lt;code&gt;v·s=0.10&lt;/code&gt;) is "calm" — p1 of −44.1% everywhere — but that calm is the C=0 world of Part 18: the market stopped believing, stopped levering, and prices the world with no backstop at all. Safety bought by killing the guarantee is not safety.&lt;/p&gt;

&lt;p&gt;Second, look at the &lt;code&gt;v·s=0.25&lt;/code&gt; column — an environment where exemptions are rarely seen and punishment is weak. From nominal C=1.00 to C=0.25, p1 runs −59.3% → −63.1% → −53.2% → −44.1%: it deepens as credibility erodes, then &lt;em&gt;lightens&lt;/em&gt; as C_eff falls through the credibility line into the no-backstop world. The cliff of Parts 18 and 23 appears again, and now its position is visibly institutional: the same market, the same temptation, different discipline — different tail.&lt;/p&gt;

&lt;p&gt;And the steepest part of the cliff is even earlier than Part 23 showed. The &lt;code&gt;v·s=2.00&lt;/code&gt; row prices a 5% exemption rate: &lt;code&gt;C_eff=0.95&lt;/code&gt;, p1 −49.4% — &lt;strong&gt;20 of the 34.3 points of cliff damage already gone at 5% exemption&lt;/strong&gt;. Part 23 said 64% of the damage sits in the first 10% of the loophole. It is worse: most of it sits in the first 5%. The credibility line is not approached gradually — the market falls toward it in the first visible doubt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the retail investor should do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Evaluate a backstop by its visibility, not its promises.&lt;/strong&gt; The first question about any "we stand ready" is not how much it can buy — it is &lt;em&gt;how fast an override would be seen&lt;/em&gt;. Independent audits, published reaction functions, conditional programs, free press, market monitoring: these are the actual vaccine. A promise with no visibility is a promise with &lt;code&gt;v≈0&lt;/code&gt;, and &lt;code&gt;v≈0&lt;/code&gt; leaks the whole credibility space regardless of punishment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trust the line, not the person.&lt;/strong&gt; North's lesson: credibility is an institutional property. The reliable backstop is not the one led by the most honest-sounding official; it is the one whose reneging is expensive. When the institutional line sits high (little transparency, weak constraints), price the market as if the promise may be empty — because below the line, it is.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Price the first doubt, not the full fall.&lt;/strong&gt; The cliff's steepest step is its first: 5% exemption already eats 20 points of the worst 1%. Half-believing is the most dangerous state (Part 18), and a backstop that is credible but unobserved is precisely half-believing — the market prices the guarantee, and the guarantee is already leaking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The spiral is the real risk.&lt;/strong&gt; Credibility is a stock, not a stance. Every visible exemption draws down the stock, and below the line the drawdown accelerates. When assessing tail risk, do not ask "will they hold this time" — ask "how much credibility remains above the line".&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The last garment
&lt;/h2&gt;

&lt;p&gt;The retail garment (Parts 19–22), the policy backstop (Parts 23–24) — the series has now priced the whole stack: mechanism, behavior, commitment, exemption, and the institutions that keep promises. What is left is the question the first part raised and every part since has deferred: when the backstop is credible, the market leans on it — and the leaning itself changes the tail. The moral-hazard loop of Part 4 returns as the final act. That is the next part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/policy_credibility.py&lt;/code&gt; (V10-P12), endogenous exemption on the Part 23 machinery, self-test and Monte Carlo included. Deterministic, 206 lines, no dependencies beyond the standard library.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Currently available for freelance work — Python pipelines, quantitative risk tooling, and AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by Giorgia Finazzi on Unsplash&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>pyhton</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Exemption Cliff: Why a 10% Policy Loophole Eats Most of the Backstop</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Sun, 04 Oct 2026 04:46:08 +0000</pubDate>
      <link>https://dev.to/fengyugbt/the-exemption-cliff-why-a-10-policy-loophole-eats-most-of-the-backstop-d5g</link>
      <guid>https://dev.to/fengyugbt/the-exemption-cliff-why-a-10-policy-loophole-eats-most-of-the-backstop-d5g</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 23 of the Fat Tail Notes series — the last garment's loophole is the policy's.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook
&lt;/h2&gt;

&lt;p&gt;Part 22 priced the retail loophole: under full commitment, an exemption rate of just 10% destroyed 85% of the garment, because as long as any override can fire, the worst 1% stays deep. The conclusion for the individual was clean: make the hole narrow.&lt;/p&gt;

&lt;p&gt;But the individual's hole only eats her own underwear. The backstop itself — the thing every portfolio leans on in a crash — has its own override, and it is the most dangerous one in the series. In 1977, Kydland and Prescott proved that a policymaker who promises one thing and, when the moment comes, does another, is not failing. He is behaving exactly as the incentive structure demands: reneging on a promise is often &lt;em&gt;optimal ex post&lt;/em&gt;. They called it the time-inconsistency problem, and it earned the Nobel Prize in 2004.&lt;/p&gt;

&lt;p&gt;This part prices the policy loophole with the same machine we used for the retail one. The answer is the mirror image of Part 22, with the same asymmetry, one new mechanism, and a verdict that lands hardest on the people who trust the backstop the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  The machine
&lt;/h2&gt;

&lt;p&gt;The market's belief in backstop capacity C — the parameter that sets trigger probability and market leverage in the P4/P6 machinery — is no longer exogenous. A policy commitment splits into two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;rule share&lt;/strong&gt; &lt;code&gt;r ∈ [0,1]&lt;/code&gt;: pre-committed, automatic rules — standing purchase programs, automatic stabilizers, conditional triggers — that are immune to discretion;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;discretionary share&lt;/strong&gt; &lt;code&gt;1 − r&lt;/code&gt;: the room the policymaker keeps to respond as events unfold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;policy exemption rate&lt;/strong&gt; &lt;code&gt;p ∈ [0,1]&lt;/code&gt; is the probability that, at the moment of crisis, the policymaker overrides the discretionary part — the political "this time is different". The effective capacity the market actually prices is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C_eff = C × (1 − (1 − r) × p)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;p = 0&lt;/code&gt;: commitment fully holds (&lt;code&gt;C_eff = C&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r = 1&lt;/code&gt;: pure rules — exemption is immunized (&lt;code&gt;C_eff = C&lt;/code&gt; for any &lt;code&gt;p&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r = 0&lt;/code&gt;: pure discretion — exemption hits at full force (&lt;code&gt;C_eff = C(1−p)&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The market then prices leverage and tail losses at &lt;code&gt;C_eff&lt;/code&gt; (same machinery as Part 18: 2,000 paths per level, seed 20260921). Because effective capacity is continuous, parameters are interpolated between the discrete policy-space levels — a 10% exemption from C=1.00 prices like a barely-doubted backstop, not like the next discrete level down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anchor: reneging is the equilibrium, not the malfunction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Time inconsistency.&lt;/strong&gt; Kydland &amp;amp; Prescott (1977, &lt;em&gt;Journal of Political Economy&lt;/em&gt;) showed that a forward-looking policymaker who cannot commit will, at each moment, do what is optimal &lt;em&gt;then&lt;/em&gt; — and that re-optimizing every period destroys the credibility of the long-run plan. The solution they proposed was precisely the one our &lt;code&gt;r&lt;/code&gt; parameter formalizes: &lt;strong&gt;rules rather than discretion&lt;/strong&gt;. A rule is a way of making reneging expensive; discretion makes it cheap. The central banking revolution of the 1980s–90s — inflation targeting, independent committees, published reaction functions — was the field's answer: bind discretion with rules so that promises become credible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rhetorical override and its limits.&lt;/strong&gt; The most famous policy promise in financial history is Mario Draghi's July 26, 2012 speech: "Within our mandate, the ECB is ready to do whatever it takes to preserve the euro. And believe me, it will be enough." The market believed him — and the striking part is &lt;em&gt;what followed&lt;/em&gt;. In September 2012 the ECB announced Outright Monetary Transactions: unlimited sovereign-bond purchases, but with strict conditionality — only for countries in an ESM adjustment program, with IMF involvement. The program was &lt;strong&gt;never activated&lt;/strong&gt;. The promise alone changed the market (the Bundesbank later noted that merely the prospect of decisive intervention altered the dynamics). Why did it work? Because OMT was the opposite of a blank check: it converted an unlimited discretionary pledge into a &lt;strong&gt;conditional, rule-like trigger&lt;/strong&gt;. The policy bought credibility by shrinking its own discretion — &lt;code&gt;r&lt;/code&gt; went up. Draghi's "believe me" worked precisely because the follow-up made the promise hard to renege on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The inflation bias.&lt;/strong&gt; Barro &amp;amp; Gordon (1983) formalized the cost: a discretionary central bank ends up with &lt;em&gt;higher&lt;/em&gt; inflation and &lt;em&gt;no&lt;/em&gt; extra output — the market prices the reneging in advance. The exemption is not free even when it never fires; the mere option to use it is priced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1 — the cliff is in the first mouthful
&lt;/h2&gt;

&lt;p&gt;Bare-market p1 (worst 1% loss) at effective capacity, pure discretion (&lt;code&gt;r = 0&lt;/code&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;p=0&lt;/th&gt;
&lt;th&gt;p=.10&lt;/th&gt;
&lt;th&gt;p=.25&lt;/th&gt;
&lt;th&gt;p=.50&lt;/th&gt;
&lt;th&gt;p=.75&lt;/th&gt;
&lt;th&gt;p=1.00&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;−29.4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−51.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−55.9%&lt;/td&gt;
&lt;td&gt;−61.3%&lt;/td&gt;
&lt;td&gt;−63.7%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;−55.9%&lt;/td&gt;
&lt;td&gt;−58.0%&lt;/td&gt;
&lt;td&gt;−60.3%&lt;/td&gt;
&lt;td&gt;−62.9%&lt;/td&gt;
&lt;td&gt;−61.4%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;−61.3%&lt;/td&gt;
&lt;td&gt;−62.0%&lt;/td&gt;
&lt;td&gt;−62.9%&lt;/td&gt;
&lt;td&gt;−63.7%&lt;/td&gt;
&lt;td&gt;−55.6%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;−63.7%&lt;/td&gt;
&lt;td&gt;−63.1%&lt;/td&gt;
&lt;td&gt;−61.4%&lt;/td&gt;
&lt;td&gt;−55.6%&lt;/td&gt;
&lt;td&gt;−49.8%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Mechanism: P4/P6 machinery, seed 20260921, 2,000 paths/level. C_eff = C×(1−p) at r=0.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Read the C=1.00 row. A policy exemption rate of &lt;strong&gt;10%&lt;/strong&gt; — the market gives the backstop a 10% chance of being overridden — moves the worst 1% from −29.4% to &lt;strong&gt;−51.3%&lt;/strong&gt;. The market barely doubts the backstop, yet the tail has already deepened by 75%. This single row eats &lt;strong&gt;21.9 of the 34.3 points&lt;/strong&gt; of damage available across the whole exemption range: &lt;strong&gt;64% of the cliff's damage happens in the first 10% of the loophole&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why? The same extreme-order logic as Part 22, through a different door. The worst 1% is decided by the deepest paths, and the deepest paths are the ones where leverage is highest — which is exactly where a marginally less-credible backstop matters most. A 10% exemption doesn't change the average market; it changes the market's &lt;em&gt;tail conditioning&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2 — the credibility-death window: safe only if trust dies
&lt;/h2&gt;

&lt;p&gt;Follow the C=1.00 row to the end: at &lt;code&gt;p=1.00&lt;/code&gt;, effective capacity is zero. The market stops believing in the backstop at all — and the tail &lt;strong&gt;lightens&lt;/strong&gt; to −44.1%, less bad than the −63.7% at &lt;code&gt;p=0.75&lt;/code&gt;. This is the mirror of Part 18's reputation cliff: the C=0 world is &lt;em&gt;safer&lt;/em&gt; than the C=0.25 world, because a market that doesn't believe doesn't lever. Exemption "fixes" the tail only by killing credibility entirely.&lt;/p&gt;

&lt;p&gt;Read it the other way. From a nominal C=0.25 (already in the half-believing abyss), additional exemption &lt;em&gt;improves&lt;/em&gt; the tail (+19.6 points at p=1.00). The paradox resolves cleanly: &lt;strong&gt;exemption damage is proportional to the trust it betrays&lt;/strong&gt;. The more the market believed the backstop, the deeper the tail when the backstop is overridden. The C=1.00 world — the world where everyone priced in the guarantee — is the world that loses the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3 — the vaccine is pass-fail
&lt;/h2&gt;

&lt;p&gt;Exemption damage at &lt;code&gt;p=0.25&lt;/code&gt;, by rule share:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;r=0.00&lt;/th&gt;
&lt;th&gt;r=0.50&lt;/th&gt;
&lt;th&gt;r=1.00&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;−26.5&lt;/td&gt;
&lt;td&gt;−23.2&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;−4.4&lt;/td&gt;
&lt;td&gt;−2.4&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;−1.6&lt;/td&gt;
&lt;td&gt;−0.9&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;+2.4&lt;/td&gt;
&lt;td&gt;+0.9&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Half the rules save 3.3 of the 26.5 points — 12%. All the rules save all of them. &lt;strong&gt;Half-rules are nearly no-rules&lt;/strong&gt;, the same all-or-nothing shape as Part 21's commitment value hiding in the last quarter. Rules immunize discretion the way commitment protected the garment: the protection arrives in the final, non-negotiable installment. Draghi's OMT is instructive here: the conditionality is what made it credible — and conditionality, in our language, is &lt;code&gt;r&lt;/code&gt; approaching 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for the retail investor
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Price the policy exemption into your tail, not the headline C.&lt;/strong&gt; Markets trade on the nominal backstop until the day they stop. Our numbers say the relevant quantity is &lt;code&gt;C_eff&lt;/code&gt;, and a 10% exemption expectation already moves the worst 1% by 21.9 points. When you read "the Fed stands ready", ask what the &lt;em&gt;override probability&lt;/em&gt; is — political cycles, election years, leadership transitions, legal challenges — not just what the announcement says.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The danger is highest exactly when trust is highest.&lt;/strong&gt; The exemption cliff punishes the believers hardest. This inverts the comfortable intuition that you are safe because everyone believes in the backstop: the more the market prices the guarantee, the more catastrophic its override.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rules are the only vaccine, and they must be all-or-nothing.&lt;/strong&gt; Automatic stabilizers, conditional purchase programs, written reaction functions — these survive exemption; discretionary room does not. Half a rule is nearly no rule. When a policymaker offers a blank "whatever it takes" with no conditions attached, the market should price the loophole — because the &lt;em&gt;lack of conditionality is itself the exemption&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"This time is different" is a policy-side phrase, not a market-side one.&lt;/strong&gt; Reinhart &amp;amp; Rogoff's eight centuries are mostly a story of sovereigns telling themselves the rules no longer apply. The retail investor's job is the opposite: to assume the rule will be overridden and price the garment for the override.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The next garment
&lt;/h2&gt;

&lt;p&gt;The policy loophole can be narrowed by rules, but the rules themselves are written by the same people who might override them. Which raises the deepest question of the series: what makes &lt;em&gt;the rule&lt;/em&gt; credible — the institution, the cost of reneging, or the fact that the exemption would be visible and punished? That is the next part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/policy_exemption.py&lt;/code&gt; (V10-P11), policy self-exemption on the Part 18 policy-space machinery, self-test and Monte Carlo included. Deterministic, 273 lines, no dependencies beyond the standard library.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Currently available for freelance work — Python pipelines, quantitative risk tooling, and AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@chenmk33?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Ming Chen&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-herd-of-animals-standing-on-top-of-a-snow-covered-field-yTikyGkACTY?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Local AI Finance Coach for My Friend's Bubble-Tea Shop - No Cloud, No API Keys</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Fri, 02 Oct 2026 04:43:11 +0000</pubDate>
      <link>https://dev.to/fengyugbt/i-built-a-local-ai-finance-coach-for-my-friends-bubble-tea-shop-no-cloud-no-api-keys-k66</link>
      <guid>https://dev.to/fengyugbt/i-built-a-local-ai-finance-coach-for-my-friends-bubble-tea-shop-no-cloud-no-api-keys-k66</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Local AI Finance Coach for My Friend's Bubble-Tea Shop - No Cloud, No API Keys
&lt;/h1&gt;

&lt;p&gt;This is built for a friend - a small business owner who runs a bubble-tea shop. Every month she exports her bank statement to reconcile her books, and every month it's the same wall of numbers: hundreds of rows of transactions, no structure she can act on. She asks the same question in three different ways - &lt;em&gt;"Is the shop actually making money? Where does it all go? What should I do differently?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Her bank statement is also exactly the kind of file she should &lt;strong&gt;never&lt;/strong&gt; upload to some AI service. It's her income, her rent, her suppliers, her staff wages. A closed API would mean shipping all of that to someone else's server.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Finance Friend&lt;/strong&gt; for exactly this person: upload an Excel/CSV export, and a small open-weight model running &lt;strong&gt;on her own machine&lt;/strong&gt; writes a plain-language report - where the money comes from, where it goes, and what to watch. No cloud. No API keys. No data leaving the laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open-source AI is the point, not a nice-to-have
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data never leaves the machine.&lt;/strong&gt; Bank statements are the last thing you want on a stranger's server. Here, the file, the statistics, and the model weights all live in one local process. A closed API fundamentally cannot make this promise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline.&lt;/strong&gt; After the one-time model download, no internet is needed. It runs in a shop basement, at a market stall, anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You own the model.&lt;/strong&gt; Qwen2.5-3B-Instruct is Apache-2.0 open weights. Swap in any GGUF - bigger, smaller, fine-tuned, another language - by changing one environment variable. No vendor lock-in, no deprecation rug pulls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; Inference is free after the model download. For a small business owner, "free forever" beats "cheap for six months" every time.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bank statement (xlsx / xls / csv)
        |
        v
analyzer.py -- pandas, on this machine
        |   auto-detects date / description / income / expense columns
        |   monthly income &amp;amp; expenses, net flow
        |   top income sources, top expense categories, biggest transactions
        |   compact JSON summary
        v
reporter.py -- llama.cpp, on this machine
        |   Qwen2.5-3B-Instruct (open weights, GGUF)
        |   250-350 word plain-language business report
        v
Gradio UI - overview table, monthly table, AI report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;The parser is forgiving on purpose - Chinese and English bank exports name their columns differently, so it matches common names loosely (Chinese exports use columns like income/expense in Chinese, which the same matcher handles):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;IN_KEYS&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;income&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deposit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;OUT_KEYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expense&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;withdrawal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_pick_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;_norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;_norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;_norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="c1"&gt;# fall back to substring matching
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The report generator is just a chat completion against a local llama.cpp model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_cpp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Llama&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Llama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen2.5-3b-q4.gguf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_chat_completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;# the compact JSON summary
&lt;/span&gt;    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;choices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole AI surface. One model file, one local process, zero network calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the AI says
&lt;/h2&gt;

&lt;p&gt;Feed it three months of a bubble-tea shop's transactions and it produces a report like this (I asked it for English here; for her, the same model writes in Chinese - the language is part of the prompt):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the period from July 1 to September 28, 2026, this small bubble-tea shop is profitable overall, with a net surplus of 6,536.07 yuan. Revenue comes mainly from in-store WeChat payments and Alipay takeout orders, which together make up most of income, and both are trending upward month over month. Spending is dominated by dairy suppliers and staff wages. Rent and utilities also stand out as large monthly costs. Overall, income exceeds spending and revenue is growing, but rent and utilities deserve attention. Recommendation: negotiate with suppliers to lower purchase costs, and review whether the current rent and utility terms can be improved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is exactly the answer to her three questions: yes, it's profitable; the money goes to dairy and staff; negotiate with suppliers and review rent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for a friend
&lt;/h2&gt;

&lt;p&gt;This is the "Build for a Friend" challenge, and the friend is real - a small business owner, exactly the person I have in mind at every design decision. The demo data is &lt;strong&gt;synthetic on purpose&lt;/strong&gt;: even a demo shouldn't run on anyone's real financial data, which is the product philosophy in a nutshell.&lt;/p&gt;

&lt;p&gt;Next step: hand the app to her, tune the report to how she actually reads it, and let her real statements run only on her own machine - never anywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/fengyuGbt/finance-friend" rel="noopener noreferrer"&gt;github.com/fengyuGbt/finance-friend&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;llama-cpp-python pandas openpyxl gradio
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; qwen2.5-3b-q4.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  https://hf-mirror.com/Qwen/Qwen2.5-3B-Instruct-GGUF/resolve/main/qwen2.5-3b-instruct-q4_k_m.gguf
python app.py   &lt;span class="c"&gt;# -&amp;gt; http://127.0.0.1:7860&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want a demo file without using your own data? &lt;code&gt;python sample_data.py&lt;/code&gt; generates a synthetic three-month statement.&lt;/p&gt;

&lt;p&gt;Built with open-source AI at its core: an Apache-2.0 open-weight model, running locally through llama.cpp - because for this friend, privacy isn't a feature, it's the whole product.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
    </item>
    <item>
      <title>One Override Is All It Takes: The Self-Exemption Loophole</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Wed, 30 Sep 2026 06:05:39 +0000</pubDate>
      <link>https://dev.to/fengyugbt/one-override-is-all-it-takes-the-self-exemption-loophole-p7c</link>
      <guid>https://dev.to/fengyugbt/one-override-is-all-it-takes-the-self-exemption-loophole-p7c</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 22 of the Fat Tail Notes series — what "this time is different" costs the retail investor.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook
&lt;/h2&gt;

&lt;p&gt;Part 21 priced the commitment device and found it all-or-nothing: full commitment pins the worst 1% at the mechanical cap, and the value lives in the last quarter. The design conclusion was blunt — never buy half a commitment.&lt;/p&gt;

&lt;p&gt;But the device has a hole that no amount of commitment strength closes. The hole is the wearer. The same investor who signed the automatic rules can, at the moment of crisis, override them. "This time is different." "I can sell just this once." "I'll renew tomorrow." Reinhart and Rogoff spent eight centuries of crisis history documenting this exact sentence — the four most expensive words in the English language, as John Templeton put it.&lt;/p&gt;

&lt;p&gt;This part prices the hole. The answer is the mirror image of Part 21's finding, and it is worse: to win, you need 100% commitment. To lose, you need a single override.&lt;/p&gt;

&lt;h2&gt;
  
  
  The machine
&lt;/h2&gt;

&lt;p&gt;We take the Part 21 setup and attack it. Full commitment (&lt;code&gt;c = 1&lt;/code&gt;) is in place: every behavioral failure — panic selling, lapsed renewal, cash raid — is rescued back to the mechanical outcome. The market shocks are the same (P4/P6 losses, seed 20260921, 2,000 paths per level), the garments are the same (put: 20% deductible, 2% premium, −22% cap; cash: 20% buffer).&lt;/p&gt;

&lt;p&gt;The new parameter is the &lt;strong&gt;exemption rate&lt;/strong&gt; &lt;code&gt;e ∈ [0,1]&lt;/code&gt;: the probability that, at the moment of crisis, the investor overrides the standing rule despite full commitment — lets the panic trade through, lets the renewal lapse, lets the raid happen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;e = 0&lt;/code&gt; — full commitment holds: Part 19's mechanical garment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e = 1&lt;/code&gt; — every impulse overrides the rules: Part 20's naive investor.&lt;/li&gt;
&lt;li&gt;Between the extremes, exemption is a &lt;strong&gt;hole in the rescue layer&lt;/strong&gt;: full commitment would rescue each failure; exemption flips a share &lt;code&gt;e&lt;/code&gt; of those rescues back into failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By construction, &lt;code&gt;e = 0&lt;/code&gt; reproduces Part 19's mechanical numbers exactly, and &lt;code&gt;e = 1&lt;/code&gt; reproduces Part 20's naive numbers exactly. Every path worsens monotonically in &lt;code&gt;e&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anchor: the loophole is real, documented, and human
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"This time is different."&lt;/strong&gt; Reinhart &amp;amp; Rogoff (&lt;em&gt;This Time Is Different: Eight Centuries of Financial Folly&lt;/em&gt;, 2009) catalogued 66 countries and 800 years of crises, and the recurring pre-crisis belief was always the same: the old rules no longer apply, the boom is different. It never was. The syndrome is not confined to governments — it is how the retail investor talks himself out of his own rules at the exact moment they matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the override is irresistible: present bias.&lt;/strong&gt; O'Donoghue &amp;amp; Rabin (1999, &lt;em&gt;American Economic Review&lt;/em&gt;) formalized the mechanism: a present-biased self discounts the future sharply, so at the moment of crisis, the cost of &lt;em&gt;not&lt;/em&gt; selling (holding through the pain) feels immediate while the benefit of the rule (a smaller worst case) feels distant. The naive self plans; the present self overrides. This is not a character flaw — it is the standard model of human time preference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The good news: friction works.&lt;/strong&gt; The most famous commitment product in the field, the SEED savings account in the Philippines (Ashraf, Karlan &amp;amp; Yin, &lt;em&gt;Quarterly Journal of Economics&lt;/em&gt;, 2006), was explicitly &lt;em&gt;not&lt;/em&gt; an ironclad lock: the saver could still access the money by going to the bank and completing the paperwork. That friction was the product. Twelve months later, clients offered the account had average balances &lt;strong&gt;81% higher&lt;/strong&gt; than the control group — and those who actually opened the account saved &lt;strong&gt;337% more&lt;/strong&gt;. The commitment worked precisely because the exemption was costly. The hole was there, but it was narrow.&lt;/p&gt;

&lt;p&gt;That is the design question our numbers now answer: how narrow does the hole have to be?&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1 — the damage is nonlinear: a 10% exemption destroys 85% of the garment
&lt;/h2&gt;

&lt;p&gt;Put p1 (worst 1% loss) per exemption rate, under full commitment:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;e=0&lt;/th&gt;
&lt;th&gt;e=.10&lt;/th&gt;
&lt;th&gt;e=.25&lt;/th&gt;
&lt;th&gt;e=.50&lt;/th&gt;
&lt;th&gt;e=1.00&lt;/th&gt;
&lt;th&gt;bare&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;−22.0%&lt;/td&gt;
&lt;td&gt;−28.0%&lt;/td&gt;
&lt;td&gt;−28.0%&lt;/td&gt;
&lt;td&gt;−28.0%&lt;/td&gt;
&lt;td&gt;−28.5%&lt;/td&gt;
&lt;td&gt;−29.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;−22.0%&lt;/td&gt;
&lt;td&gt;−28.7%&lt;/td&gt;
&lt;td&gt;−44.6%&lt;/td&gt;
&lt;td&gt;−50.4%&lt;/td&gt;
&lt;td&gt;−52.2%&lt;/td&gt;
&lt;td&gt;−55.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;−22.0%&lt;/td&gt;
&lt;td&gt;−42.1%&lt;/td&gt;
&lt;td&gt;−52.4%&lt;/td&gt;
&lt;td&gt;−56.1%&lt;/td&gt;
&lt;td&gt;−58.0%&lt;/td&gt;
&lt;td&gt;−61.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;−22.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−56.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−59.9%&lt;/td&gt;
&lt;td&gt;−60.8%&lt;/td&gt;
&lt;td&gt;−61.9%&lt;/td&gt;
&lt;td&gt;−63.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;−22.0%&lt;/td&gt;
&lt;td&gt;−36.9%&lt;/td&gt;
&lt;td&gt;−39.5%&lt;/td&gt;
&lt;td&gt;−41.1%&lt;/td&gt;
&lt;td&gt;−42.4%&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Mechanism: Part 19/20/21 machinery, seed 20260921, 2,000 paths/level. C = market's belief in backstop capacity; e = exemption rate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Read the C=0.25 row. Full commitment pins the tail at −22.0%. An exemption rate of &lt;strong&gt;10%&lt;/strong&gt; — the investor overrides the rules one time in ten — jumps the worst 1% to &lt;strong&gt;−56.2%&lt;/strong&gt;. That is 85.5% of the garment's entire protection destroyed by one-tenth of a loophole.&lt;/p&gt;

&lt;p&gt;Why? The same extreme-order logic that made commitment all-or-nothing in Part 21. p1 is the worst 1%: as long as &lt;em&gt;any&lt;/em&gt; override can fire, the deepest paths are exposed to it, and with hundreds of failed paths in the depleted world, the worst 1% is almost guaranteed to include an override. The tail does not care that 90% of the time the rules hold. The tail is decided by the paths where they did not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2 — the asymmetry: win needs 100%, lose needs one
&lt;/h2&gt;

&lt;p&gt;The damage table makes the asymmetry exact (points of p1 eaten by exemption, put):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;e=.10&lt;/th&gt;
&lt;th&gt;e=.25&lt;/th&gt;
&lt;th&gt;e=.50&lt;/th&gt;
&lt;th&gt;e=1.00&lt;/th&gt;
&lt;th&gt;total protection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;6.7&lt;/td&gt;
&lt;td&gt;22.6&lt;/td&gt;
&lt;td&gt;28.4&lt;/td&gt;
&lt;td&gt;30.2&lt;/td&gt;
&lt;td&gt;30.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;20.1&lt;/td&gt;
&lt;td&gt;30.4&lt;/td&gt;
&lt;td&gt;34.1&lt;/td&gt;
&lt;td&gt;36.0&lt;/td&gt;
&lt;td&gt;36.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;34.2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;37.9&lt;/td&gt;
&lt;td&gt;38.8&lt;/td&gt;
&lt;td&gt;39.9&lt;/td&gt;
&lt;td&gt;39.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;14.9&lt;/td&gt;
&lt;td&gt;17.5&lt;/td&gt;
&lt;td&gt;19.1&lt;/td&gt;
&lt;td&gt;20.3&lt;/td&gt;
&lt;td&gt;20.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Share of protection destroyed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;e=.10&lt;/th&gt;
&lt;th&gt;e=.25&lt;/th&gt;
&lt;th&gt;e=.50&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;92.7%&lt;/td&gt;
&lt;td&gt;92.7%&lt;/td&gt;
&lt;td&gt;92.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;22.1%&lt;/td&gt;
&lt;td&gt;74.8%&lt;/td&gt;
&lt;td&gt;94.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;55.7%&lt;/td&gt;
&lt;td&gt;84.4%&lt;/td&gt;
&lt;td&gt;94.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;94.9%&lt;/td&gt;
&lt;td&gt;97.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;73.1%&lt;/td&gt;
&lt;td&gt;86.0%&lt;/td&gt;
&lt;td&gt;93.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pair this with Part 21's table: commitment of 0 → 0.75 bought 3.3 points in the depleted world; exemption of 0 → 0.10 destroys 34.2. The device is violently asymmetric. Building commitment is a grind — each additional quarter of automation buys diminishing tail improvement until the last one, which buys everything. Destroying it is trivial — one tenth of a loophole eats most of it. In engineering terms: the mechanism has a sharp reliability cliff. The rules must work 100% of the time; they are not graded, they are pass-fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3 — the loophole widens exactly when it is most dangerous
&lt;/h2&gt;

&lt;p&gt;The destruction share rises with capacity drain: 22.1% in the C=0.75 world, 55.7% in C=0.50, 85.5% in C=0.25 — and the absolute damage rises from 6.7 to 34.2 points. The calm world is the one exception (92.7% share, but only 6.0 points total): there, protection was small to begin with and the tail is dominated by a handful of paths, so any override saturates it.&lt;/p&gt;

&lt;p&gt;The pattern is the series' recurring clock: the less credible the backstop, the deeper the tail, the more behavioral failure costs — and therefore the more a single exemption costs. The self-loophole is priced in the same currency as the policy clock. When the market half-believes the backstop (C=0.25), the investor's own "this time is different" is worth −34 points of worst-case loss. The exemption is cheapest to grant and most expensive to pay, exactly at the same moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the retail investor should actually do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make exemption expensive.&lt;/strong&gt; The SEED lesson, quantified: the product worked (+81% balances) because accessing the money required going to the bank. Design the escape hatch to have friction — a cooling-off period, a written reason, a second signature. Every hour of friction is points of p1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do not install an "except this once" button.&lt;/strong&gt; Present bias peaks exactly at the moment of crisis — that is when the override is most tempting and most expensive. An easy override is not a feature, it is a −34 point feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Move the exemption decision out of the present.&lt;/strong&gt; Write the exemption criteria in advance: "I may override the rule only if X, Y, Z are all true" — and make the list narrow and concrete. The sophisticated self of O'Donoghue-Rabin commits the naive self; the present self should have no discretionary authority.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The extreme design exists: make the commitment unbreakable.&lt;/strong&gt; Ulysses did not leave himself an override — he told his crew to tie him tighter and ignore his orders. Some products do this structurally (irrevocable accounts, third-party co-signers). The model's verdict is clear: the value of the garment is 39.9 points, and it survives only while the hole stays shut.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The next hole
&lt;/h2&gt;

&lt;p&gt;The loophole can be narrowed, but the wearer remains the weakest layer. What happens when the exemption is not the investor's whim but the &lt;em&gt;policy's&lt;/em&gt;? The backstop itself — the thing the market believes in — has its own "this time is different" moment. When the commitment that matters is the government's, and the override is political, the tail we have been measuring changes shape. That is the next part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/self_exemption.py&lt;/code&gt; (V10-P10), exemption-hole model on the Part 21 commitment layer, self-test and Monte Carlo included. Deterministic, 250 lines, no dependencies beyond the standard library.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Currently available for freelance work — Python pipelines, quantitative risk tooling, and AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@haseebjkhan?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Haseeb Jamil&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/snowy-mountain-peak-under-starry-sky-3s85IxVDyXE?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Commitment Is All-or-Nothing: Why the Last Quarter of the Garment Carries the Tail</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Tue, 29 Sep 2026 08:41:15 +0000</pubDate>
      <link>https://dev.to/fengyugbt/commitment-is-all-or-nothing-why-the-last-quarter-of-the-garment-carries-the-tail-4ca7</link>
      <guid>https://dev.to/fengyugbt/commitment-is-all-or-nothing-why-the-last-quarter-of-the-garment-carries-the-tail-4ca7</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 21 of the Fat Tail Notes series — pricing the "better default" the retail investor actually needs.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook
&lt;/h2&gt;

&lt;p&gt;Part 20 ended with a claim: the retail investor does not need a better put, they need a better default. The behavioral gap had eaten 73–96% of the garment's nominal protection, and the binding constraint was the wearer, not the contract. Panic sells, lapses, and cash raids — the garment falls off exactly when the tail arrives.&lt;/p&gt;

&lt;p&gt;So the design question is unavoidable: what is a &lt;em&gt;better default&lt;/em&gt; actually worth? Is it a free lunch, a cheap lunch, or a trap? And — the question nobody asks — does it have a shape? Does half a commitment buy half the protection, or does the value hide somewhere unexpected?&lt;/p&gt;

&lt;p&gt;This part prices it. The answer is uncomfortable: on tail metrics, commitment is &lt;strong&gt;all-or-nothing&lt;/strong&gt;. The first three quarters of a commitment buy almost nothing. The last quarter buys almost everything. And the reason is not the cost — it is the mathematics of the worst 1%.&lt;/p&gt;

&lt;h2&gt;
  
  
  The machine
&lt;/h2&gt;

&lt;p&gt;We reuse the machinery of Parts 19 and 20 exactly. The market shocks are the same (P4/P6 losses, seed 20260921, 2,000 paths per level). The garments are the same: the put (20% deductible, 2% premium, hard cap at −22%) and the cash buffer (20%, geometric scaling). The behavioral failures are the same: panic selling (40% of deep paths, locking −28%), lapsed renewal (40%, back to bare), cash raids (25%, back to bare).&lt;/p&gt;

&lt;p&gt;The new parameter is &lt;strong&gt;commitment strength&lt;/strong&gt; &lt;code&gt;c ∈ [0,1]&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c = 0&lt;/code&gt; — the naive investor of Part 20. Every behavioral failure fires at full force.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c = 1&lt;/code&gt; — the mechanical garment of Part 19. Standing renewal, separate accounts, written rules: zero behavioral failure.&lt;/li&gt;
&lt;li&gt;Between the extremes, commitment is a &lt;strong&gt;rescue layer&lt;/strong&gt;: the same shocks, the same impulses occur, and each failed path is rescued back to the mechanical outcome with probability &lt;code&gt;c&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By construction, &lt;code&gt;c = 0&lt;/code&gt; reproduces Part 20's published numbers exactly, and &lt;code&gt;c = 1&lt;/code&gt; (gross of cost) reproduces Part 19's mechanical numbers. Every path improves monotonically in &lt;code&gt;c&lt;/code&gt;. Commitment is not free: it costs a fraction of assets per year, &lt;code&gt;0.5%&lt;/code&gt; per full unit in the cheap scenario, &lt;code&gt;2%&lt;/code&gt; in the expensive one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anchor: real commitment devices are absurdly cheap
&lt;/h2&gt;

&lt;p&gt;Before the numbers, the field evidence — because it frames what "commitment costs" should mean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save More Tomorrow&lt;/strong&gt; (Thaler &amp;amp; Benartzi, &lt;em&gt;Journal of Political Economy&lt;/em&gt;, 2004): employees were offered a plan that commits a &lt;em&gt;portion of future raises&lt;/em&gt; to savings automatically. 78% of those offered joined. 80% stayed through four pay raises. Average savings rates rose from 3.5% to 11.6% in 28 months (13.6% over 40 months in the JPE sample). No pain, no lapse — because the commitment was executed before the money ever arrived.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;401(k) automatic enrollment&lt;/strong&gt; (Madrian &amp;amp; Shea, 2001): the same company, the same plan, one change — the default flipped from "not enrolled unless you opt in" to "enrolled unless you opt out." Participation among new employees jumped from roughly 40% to 86%. A default option is free. It moved 46 points of participation.&lt;/p&gt;

&lt;p&gt;And the caution from Choi et al.: automatic enrollees mostly &lt;em&gt;stay&lt;/em&gt; at the default 3% contribution rate. The default is powerful precisely because it is sticky — which is why designing the default well matters. Commitment devices work; what they are committed &lt;em&gt;to&lt;/em&gt; is the design question.&lt;/p&gt;

&lt;p&gt;The common thread: &lt;strong&gt;these interventions cost almost nothing and move behavior massively&lt;/strong&gt;. That maps onto our model as the cost line — and the model's answer is blunt: cost is not the constraint. The constraint is whether you commit at all, and how far.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1 — commitment is all-or-nothing
&lt;/h2&gt;

&lt;p&gt;Gross behavioral put p1 (worst 1% loss) per commitment level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;c=0&lt;/th&gt;
&lt;th&gt;c=0.25&lt;/th&gt;
&lt;th&gt;c=0.50&lt;/th&gt;
&lt;th&gt;c=0.75&lt;/th&gt;
&lt;th&gt;c=1.00&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;−28.5%&lt;/td&gt;
&lt;td&gt;−28.3%&lt;/td&gt;
&lt;td&gt;−28.0%&lt;/td&gt;
&lt;td&gt;−28.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;−52.2%&lt;/td&gt;
&lt;td&gt;−52.0%&lt;/td&gt;
&lt;td&gt;−49.9%&lt;/td&gt;
&lt;td&gt;−46.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;−58.0%&lt;/td&gt;
&lt;td&gt;−57.1%&lt;/td&gt;
&lt;td&gt;−55.7%&lt;/td&gt;
&lt;td&gt;−53.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;−61.9%&lt;/td&gt;
&lt;td&gt;−61.3%&lt;/td&gt;
&lt;td&gt;−60.7%&lt;/td&gt;
&lt;td&gt;−58.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;−42.4%&lt;/td&gt;
&lt;td&gt;−41.8%&lt;/td&gt;
&lt;td&gt;−41.3%&lt;/td&gt;
&lt;td&gt;−39.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Mechanism: Part 19/20 machinery, seed 20260921, 2,000 paths/level. C = market's belief in backstop capacity; c = commitment strength.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now the marginal value — the points of p1 improvement each quarter of commitment buys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;c: 0→.25&lt;/th&gt;
&lt;th&gt;.25→.5&lt;/th&gt;
&lt;th&gt;.5→.75&lt;/th&gt;
&lt;th&gt;.75→1&lt;/th&gt;
&lt;th&gt;total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;3.7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24.2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;0.9&lt;/td&gt;
&lt;td&gt;1.4&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;31.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;36.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;td&gt;2.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;39.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;1.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the C=0.25 row. The first 75% of commitment — rescue three out of four behavioral failures — buys 3.3 points (−61.9% → −58.6%). The last 25% buys &lt;strong&gt;36.6 points&lt;/strong&gt;. Nine-tenths of the entire protection of commitment lives in the final quarter.&lt;/p&gt;

&lt;p&gt;Why? Because p1 is the worst 1% — an extreme-order statistic. As long as &lt;em&gt;any&lt;/em&gt; behavioral failure survives, the worst paths are deep losses: a single un-rescued panic locks −28%; a single lapse returns the investor to bare, where the tail is −60% territory. At &lt;code&gt;c = 0.75&lt;/code&gt;, 25% of failures still fire — and with hundreds of failed paths, the worst 1% is almost guaranteed to be one of them. The tail only pins once the rescue rate is near 1. The mechanical cap of −22% is the "all"; the behavioral tail is the "nothing"; between them there is almost no transition.&lt;/p&gt;

&lt;p&gt;Intuition says half a commitment buys half the protection. The tail says: half a commitment is a waste of half a commitment. On p1, commitment is binary.&lt;/p&gt;

&lt;p&gt;(The cash garment shows the same shape, muted: in the depleted world, the first 0.75 of commitment buys 3.1 points, the last 0.25 buys 7.7 points.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2 — cost is not the constraint
&lt;/h2&gt;

&lt;p&gt;Net p1 with commitment cost — including the expensive 2%/yr scenario:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;bare p1&lt;/th&gt;
&lt;th&gt;net put p1 @ c*=1.00 (2% cost)&lt;/th&gt;
&lt;th&gt;improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;−29.4%&lt;/td&gt;
&lt;td&gt;−24.0%&lt;/td&gt;
&lt;td&gt;5.4 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;−55.9%&lt;/td&gt;
&lt;td&gt;−24.0%&lt;/td&gt;
&lt;td&gt;31.9 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;−61.3%&lt;/td&gt;
&lt;td&gt;−24.0%&lt;/td&gt;
&lt;td&gt;37.3 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;−63.7%&lt;/td&gt;
&lt;td&gt;−24.0%&lt;/td&gt;
&lt;td&gt;39.7 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;−44.1%&lt;/td&gt;
&lt;td&gt;−24.0%&lt;/td&gt;
&lt;td&gt;20.1 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The optimal commitment strength &lt;code&gt;c*&lt;/code&gt; is &lt;strong&gt;1.00 in every world, at both costs&lt;/strong&gt;. Not 0.90, not 0.75 — full. Even the expensive scenario (2%/yr) cannot dent full commitment, because the last quarter alone delivers 6–37 points of protection against at most 0.5 points of extra cost.&lt;/p&gt;

&lt;p&gt;This is the model's version of the 401(k) finding: the default flip costs nothing and moves 46 points. Commitment devices are not priced like options or insurance — they are priced like &lt;em&gt;software settings&lt;/em&gt;. The binding constraint was never money. It is the decision to automate at all — and the discipline to automate &lt;em&gt;fully&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3 — the value of commitment scales with capacity drain
&lt;/h2&gt;

&lt;p&gt;The improvement column above is the story: in the calm world (C=1.00), full commitment buys 5.4 points net. In the depleted world (C=0.25) — where the market half-believes the backstop and leverage is at its highest — it buys &lt;strong&gt;39.7 points&lt;/strong&gt;, 7.3× more.&lt;/p&gt;

&lt;p&gt;Same mechanism as Part 19's put-vs-cash gap: the deeper the tail, the more the mechanical execution matters, because the deeper the tail, the more behavioral failure costs. The garment is worth the most exactly when the policy clock is most drained — when the investor is most tempted to improvise. Commitment is cheap insurance against improvisation, and improvisation is most dangerous precisely when the backstop is least credible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the retail investor should actually do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never buy half a commitment.&lt;/strong&gt; Automatic renewal, panic-lock rules, and separate accounts go together. Three out of four is worth 3 points in the worst world; four out of four is worth 40. The tail punishes partial automation with disproportionate cruelty.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do not bargain over the cost of the commitment device.&lt;/strong&gt; At any plausible price, full commitment wins. The expensive scenarios in this model were still dominated by the last quarter of protection. The question is not "can I afford it" — it is "will I let it run."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Put the commitment where the improvisation is.&lt;/strong&gt; The biggest behavioral channel in every world is panic selling (the failure that &lt;em&gt;deepens&lt;/em&gt; losses beyond the mechanical cap). The second is lapse (returning to bare). A default that auto-renews and auto-executes is worth more than a cleverer contract — this is the 401(k) lesson restated: the default &lt;em&gt;is&lt;/em&gt; the policy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Design the default, don't just flip it.&lt;/strong&gt; Choi et al.'s caution applies: automatic enrollees stay at the default 3%. A commitment device that automates the wrong level (too little buffer, too high a deductible, renewal priced at the wrong moment) locks in the wrong answer with the same sticky power. The stickiness is the product; get the default right before you make it automatic.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The next hole
&lt;/h2&gt;

&lt;p&gt;The garment now has all four layers: the mechanism (Part 19), the behavior (Part 20), the commitment (this part), and the cost (this part). But commitment has a weakness the model cannot price — the wearer who exempts themselves. "This time it's different." The self-exemption loophole, and what it does to the all-or-nothing curve, is the next part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/commitment_device.py&lt;/code&gt; (V10-P9), rescue-layer commitment model, self-test and Monte Carlo included. Deterministic, 281 lines, no dependencies beyond the standard library.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Currently available for freelance work — Python pipelines, quantitative risk tooling, and AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@nmatz_99?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Nico M.&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/grayscale-photo-of-person-standing-on-sea-waves-_opukzKz-ps?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Behavioral Gap: What Protection Actually Delivers After Panic, Lapse, and Raids</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Mon, 28 Sep 2026 12:21:33 +0000</pubDate>
      <link>https://dev.to/fengyugbt/the-behavioral-gap-what-protection-actually-delivers-after-panic-lapse-and-raids-5cml</link>
      <guid>https://dev.to/fengyugbt/the-behavioral-gap-what-protection-actually-delivers-after-panic-lapse-and-raids-5cml</guid>
      <description>&lt;p&gt;&lt;em&gt;Fat Tail Notes · Part 20 · V10-P8&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Part 19 priced the retail garment and found something encouraging: a 2% cash buffer and a 2% put both improve the tail at every policy-capacity level, and at C=0.25 the put's nominal tail protection reaches 41.7 points. Then Part 19 ended with the uncomfortable question this part answers with numbers: the garments are mechanical in the model, but they are worn by people. What does the protection actually deliver once panic selling, lapsed renewals, and raided cash buffers are priced in?&lt;/p&gt;

&lt;p&gt;The answer is stark. &lt;strong&gt;The behavioral gap eats 73% to 96% of nominal protection.&lt;/strong&gt; At C=0.25, the put that nominally saves 41.7 points on the 1st percentile actually saves 1.8 points once people are in the model. The underwear works on paper and falls off in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What the crisis does to retail behavior
&lt;/h2&gt;

&lt;p&gt;The 2020 evidence is the anchor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The S&amp;amp;P 500 fell 34% in 33 days through March 23, 2020, with single-day drops of 9.5% and 12.9%.&lt;/li&gt;
&lt;li&gt;Vanguard's study of its US retail investors (2/19–5/31): only 17% traded at all, and fewer than 0.5% panic-liquidated everything. The garment mostly stayed on — but the minority who took it off did so at the worst moment, and lagged in the rebound.&lt;/li&gt;
&lt;li&gt;The Indian market tells the other side: SEBI data show retail investors withdrew about ₹25,000 crore from equity mutual funds in March–May 2020 — just before an 80% rebound. The ones who sold locked their losses and missed the recovery.&lt;/li&gt;
&lt;li&gt;The behavior literature is old and consistent: loss aversion (Kahneman &amp;amp; Tversky) makes losses feel about 2-2.5x as bad as equal gains; the disposition effect (Shefrin &amp;amp; Statman) makes investors sell winners early and ride losers down. Panic selling is the tail of those biases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the experience literature explains who wears the garment: Malmendier &amp;amp; Nagel's Depression Babies (QJE 2011) show that people who have lived through big crashes shy away from stocks for years or decades — lower participation, lower equity share. The first-time investor who has never seen a bear market is exactly the person who panics first.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The model: three failure channels, one behavioral clock
&lt;/h2&gt;

&lt;p&gt;For readers new to options, a plain-language note on what the "put garment" is. A put is a contract: you keep your stocks and buy the right to sell them at a fixed price — say 20% below today's level. If the market falls through that strike, you sell at the agreed price and your loss is capped at the deductible; the deeper the drop, the less it can hurt you. It is insurance: you pay a premium (Part 19 priced it at 2%/yr) and the option seller absorbs the tail. In this series the put garment means deductible 20%, premium 2%/yr, losses truncated at -22% — the "hard cap" in the tables. The behavioral layer below shows what happens when that insurance is sold in a panic (the cap softens to -28%) or simply not renewed (the garment vanishes and the path reverts to bare) — which is why the right product is not a better put: the contract already works. The binding constraint is the wearer, someone who must decide, mid-crisis, to keep paying and to keep holding.&lt;/p&gt;

&lt;p&gt;We reuse the market-loss machinery of Parts 15-19 (P4's &lt;code&gt;run_policy_moral_hazard&lt;/code&gt;, P6's capacity levels, P7's garments). On top of the mechanical garments we add three behavioral failure channels, each triggered by the crisis itself (seed 20260921, 2,000 paths per level):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Panic sell&lt;/strong&gt; — when a path passes -25%, the investor sells and locks a realized loss at -28% (three points of panic slippage), with probability 40%. The option garment's hard cap softens from -22% to -28%, and the garment is gone for that path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lapse&lt;/strong&gt; — the option renewal, repriced at crisis cost (Part 19's 6% world), is not paid with probability 40%. The put garment vanishes and the path reverts to bare.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cash raid&lt;/strong&gt; — the cash buffer is spent under stress with probability 25%. The cash garment vanishes and the path reverts to bare.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An experienced investor — who has lived through a crisis — has failure rates roughly halved (Malmendier &amp;amp; Nagel in reverse). The question is how much that experience is worth in tail space.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The behavioral gap: nominal vs actual protection
&lt;/h2&gt;

&lt;p&gt;Full table (per policy capacity C; bare = no garment, mech = Part 19's numbers, bhv = behavioral):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;garment&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;p10&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;worst&lt;/th&gt;
&lt;th&gt;&amp;gt;30%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-24.6%&lt;/td&gt;
&lt;td&gt;-27.6%&lt;/td&gt;
&lt;td&gt;-29.4%&lt;/td&gt;
&lt;td&gt;-31.5%&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;mech cash&lt;/td&gt;
&lt;td&gt;-19.7%&lt;/td&gt;
&lt;td&gt;-22.1%&lt;/td&gt;
&lt;td&gt;-23.5%&lt;/td&gt;
&lt;td&gt;-25.2%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;bhv cash&lt;/td&gt;
&lt;td&gt;-20.9%&lt;/td&gt;
&lt;td&gt;-25.4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-28.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-30.2%&lt;/td&gt;
&lt;td&gt;0.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;mech put&lt;/td&gt;
&lt;td&gt;-21.5%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;bhv put&lt;/td&gt;
&lt;td&gt;-23.6%&lt;/td&gt;
&lt;td&gt;-28.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-28.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-30.3%&lt;/td&gt;
&lt;td&gt;0.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-30.7%&lt;/td&gt;
&lt;td&gt;-47.8%&lt;/td&gt;
&lt;td&gt;-55.9%&lt;/td&gt;
&lt;td&gt;-61.6%&lt;/td&gt;
&lt;td&gt;27.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;mech cash&lt;/td&gt;
&lt;td&gt;-24.5%&lt;/td&gt;
&lt;td&gt;-38.3%&lt;/td&gt;
&lt;td&gt;-44.7%&lt;/td&gt;
&lt;td&gt;-49.3%&lt;/td&gt;
&lt;td&gt;23.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;bhv cash&lt;/td&gt;
&lt;td&gt;-26.1%&lt;/td&gt;
&lt;td&gt;-40.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-52.9%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-60.8%&lt;/td&gt;
&lt;td&gt;24.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;mech put&lt;/td&gt;
&lt;td&gt;-21.6%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;bhv put&lt;/td&gt;
&lt;td&gt;-25.7%&lt;/td&gt;
&lt;td&gt;-28.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-52.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-58.1%&lt;/td&gt;
&lt;td&gt;7.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-38.6%&lt;/td&gt;
&lt;td&gt;-55.4%&lt;/td&gt;
&lt;td&gt;-61.3%&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;54.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;mech cash&lt;/td&gt;
&lt;td&gt;-30.8%&lt;/td&gt;
&lt;td&gt;-44.3%&lt;/td&gt;
&lt;td&gt;-49.0%&lt;/td&gt;
&lt;td&gt;-52.9%&lt;/td&gt;
&lt;td&gt;49.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;bhv cash&lt;/td&gt;
&lt;td&gt;-32.8%&lt;/td&gt;
&lt;td&gt;-48.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-58.1%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-64.4%&lt;/td&gt;
&lt;td&gt;50.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;mech put&lt;/td&gt;
&lt;td&gt;-21.8%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;bhv put&lt;/td&gt;
&lt;td&gt;-27.9%&lt;/td&gt;
&lt;td&gt;-44.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-58.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;12.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-47.1%&lt;/td&gt;
&lt;td&gt;-60.5%&lt;/td&gt;
&lt;td&gt;-63.7%&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;80.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;mech cash&lt;/td&gt;
&lt;td&gt;-37.6%&lt;/td&gt;
&lt;td&gt;-48.4%&lt;/td&gt;
&lt;td&gt;-51.0%&lt;/td&gt;
&lt;td&gt;-52.9%&lt;/td&gt;
&lt;td&gt;73.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;bhv cash&lt;/td&gt;
&lt;td&gt;-40.1%&lt;/td&gt;
&lt;td&gt;-53.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-61.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-64.8%&lt;/td&gt;
&lt;td&gt;76.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;mech put&lt;/td&gt;
&lt;td&gt;-21.9%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;bhv put&lt;/td&gt;
&lt;td&gt;-30.1%&lt;/td&gt;
&lt;td&gt;-53.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-61.9%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;18.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-33.9%&lt;/td&gt;
&lt;td&gt;-41.0%&lt;/td&gt;
&lt;td&gt;-44.1%&lt;/td&gt;
&lt;td&gt;-46.7%&lt;/td&gt;
&lt;td&gt;84.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;mech cash&lt;/td&gt;
&lt;td&gt;-27.1%&lt;/td&gt;
&lt;td&gt;-32.8%&lt;/td&gt;
&lt;td&gt;-35.3%&lt;/td&gt;
&lt;td&gt;-37.3%&lt;/td&gt;
&lt;td&gt;38.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;bhv cash&lt;/td&gt;
&lt;td&gt;-28.9%&lt;/td&gt;
&lt;td&gt;-37.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-42.6%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-46.5%&lt;/td&gt;
&lt;td&gt;50.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;mech put&lt;/td&gt;
&lt;td&gt;-20.7%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;bhv put&lt;/td&gt;
&lt;td&gt;-25.9%&lt;/td&gt;
&lt;td&gt;-37.4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-42.4%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-46.5%&lt;/td&gt;
&lt;td&gt;20.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mechanical column is Part 19. The behavioral column is the same instrument worn by people. The difference is the behavioral gap — and it is measured in points of tail protection.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The gap, in points
&lt;/h2&gt;

&lt;p&gt;Nominal vs actual p1 improvement over bare:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;put: nominal&lt;/th&gt;
&lt;th&gt;put: actual&lt;/th&gt;
&lt;th&gt;cash: nominal&lt;/th&gt;
&lt;th&gt;cash: actual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;7.4 pts&lt;/td&gt;
&lt;td&gt;0.9 pts&lt;/td&gt;
&lt;td&gt;5.9 pts&lt;/td&gt;
&lt;td&gt;1.4 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;33.9 pts&lt;/td&gt;
&lt;td&gt;3.7 pts&lt;/td&gt;
&lt;td&gt;11.2 pts&lt;/td&gt;
&lt;td&gt;3.0 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;39.3 pts&lt;/td&gt;
&lt;td&gt;3.3 pts&lt;/td&gt;
&lt;td&gt;12.3 pts&lt;/td&gt;
&lt;td&gt;3.2 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;41.7 pts&lt;/td&gt;
&lt;td&gt;1.8 pts&lt;/td&gt;
&lt;td&gt;12.7 pts&lt;/td&gt;
&lt;td&gt;2.0 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;22.1 pts&lt;/td&gt;
&lt;td&gt;1.8 pts&lt;/td&gt;
&lt;td&gt;8.8 pts&lt;/td&gt;
&lt;td&gt;1.6 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three readings from the numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: the garment fails by disappearing, not by worsening.&lt;/strong&gt; Lapse and raid do not degrade the protection — they erase it, sending the path back to the bare distribution. Panic does not merely soften the cap; it replaces the garment with a realized loss. That is why the gap is so large: the worst paths in the behavioral distribution are not "protected but worse" paths, they are &lt;em&gt;unprotected&lt;/em&gt; paths. The underwear either stays on or it is gone; there is no in-between. And the failures are the tail: panic share rises monotonically as capacity drains, from 20.5% of paths at C=1.00 to 38.2% at C=0.25 — 20.5% -&amp;gt; 38.2% of deep paths end in a panic sale exactly where the garment mattered most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: the behavioral clock runs in the same direction as the policy clock.&lt;/strong&gt; Part 18 showed the backstop is weakest when most needed. Part 20 shows the retail garment is most likely to fall off when most needed. At C=1.00 (calm world, shallow crises), panic triggers on 20.5% of paths and the behavioral put still keeps the tail near -28%. At C=0.25 (depleted backstop, deep crises), panic triggers on 38.2% of paths, lapses and raids push p1 back toward bare, and the put that nominally saves 41.7 points actually saves 1.8. The two clocks — policy capacity and behavioral discipline — run in the same direction, and both fail hardest in the same world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third: experience is worth more in moderate crises than in extreme ones.&lt;/strong&gt; Halving the failure rates (the experienced investor) improves the behavioral put p1 by 0.5 pts at C=1.00, 3.4 pts at C=0.75, 0.2 pts at C=0.50, 0.7 pts at C=0.25, 0.8 pts at C=0.00. Experience is most valuable at C=0.75 — the moderate-crisis world where the garment still has a chance — and nearly useless at the extreme: at C=0.25 the tail is so deep that even halved failure rates leave enough lapsed and raided paths to dominate the 1st percentile. The Depression-babies effect is real, and it is a medium-crisis effect. In the extreme world, no amount of lived experience keeps the underwear on.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What the numbers say a retail investor should do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The garment is necessary but not sufficient. The binding constraint is not the price of protection — it is the wearer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three practical moves follow from the structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make the garment automatic.&lt;/strong&gt; Panic, lapse, and raid are all discretionary acts that the model prices as probabilities. Pre-commitment removes the discretion: automatic rolling puts with standing renewal instructions, cash in a separate account you cannot raid by reflex, a written rule ("I sell nothing in the first 72 hours after a -25% print") that exists before the panic does. The 2020 Vanguard number — under 0.5% of investors panic-liquidated — suggests most people keep the garment on; the ones who do not are the ones whose tail is unprotected, and automation is the cheapest way to move yourself out of that minority.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do not let the calm world fool you into a bigger tail.&lt;/strong&gt; The behavioral gap at C=1.00 looks small (0.9-1.4 pts saved), but C is not a constant: Part 18's clock reads policy capacity, and it drains. The gap is largest exactly where the garment is most needed. Price the protection for the C=0.25 world while the market still looks like C=1.00.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Price your own experience honestly.&lt;/strong&gt; If you have never lived through a bear market, your failure rates are the naive column — the model says halving them buys 0.5-3.4 points of tail. The cheapest way to halve them is not more information; it is structure: written rules, automatic flows, and a garment that does not require you to make a good decision in the middle of a 9.5% down day.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Where this leaves the series
&lt;/h2&gt;

&lt;p&gt;Part 18 priced the backstop's clock. Part 19 priced the garment. Part 20 priced the wearer. The series' four layers are now complete in one direction: the market's own protection is probabilistic (policy clock), the retail garment that must exist is priced (cash and puts, both ~2%/yr), and the garment's actual delivery is gated by behavior — 73-96% of nominal protection is eaten by panic, lapse, and raids, with the gap widest exactly where the tail is deepest.&lt;/p&gt;

&lt;p&gt;The remaining question is the one the model cannot answer: the garment is an insurance contract, but wearing it is a commitment device. If the numbers say the binding constraint is behavior, then the product a retail investor actually needs is not a better put — it is a better default. That is the hook for Part 21.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/behavioral_underwear.py&lt;/code&gt; (V10-P8), self-test and Monte Carlo included. Deterministic, stdlib only. Numbers above are exact output with seed 20260921, market losses from the P4/P6 machinery, garments from P7, behavioral failure rates: panic 40% (trigger -25%, lock -28%), lapse 40%, cash raid 25%, experienced = halved.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Behavioral anchors: March 2020 (S&amp;amp;P -34% in 33 days; Vanguard study — 17% traded, &amp;lt;0.5% panic-liquidated; India SEBI — ₹25,000 crore withdrawn Mar-May 2020 before an 80% rebound); Kahneman &amp;amp; Tversky loss aversion; Shefrin &amp;amp; Statman disposition effect; Malmendier &amp;amp; Nagel Depression Babies (QJE 2011, NBER w14813) and experience effects (NBER w29074).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Available for freelance work — Python pipelines, quantitative risk tooling, AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@dlerman6?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Daniel Lerman&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-large-storm-moving-across-the-sky-over-a-body-of-water-GBkssTODNT0?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Retail Underwear: What Protection Costs When the Backstop Has a Clock</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Sun, 27 Sep 2026 11:17:32 +0000</pubDate>
      <link>https://dev.to/fengyugbt/the-retail-underwear-what-protection-costs-when-the-backstop-has-a-clock-5f68</link>
      <guid>https://dev.to/fengyugbt/the-retail-underwear-what-protection-costs-when-the-backstop-has-a-clock-5f68</guid>
      <description>&lt;p&gt;&lt;em&gt;Fat Tail Notes · Part 19 · V10-P7&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Part 18 asked the question this series has been walking toward since Part 11: when the backstop's protection is probabilistic, what does the retail investor's own "underwear" actually consist of, and what does it cost? This part answers with numbers. Two garments exist for the unprotected investor — a cash buffer and put insurance — and they have different geometries, different costs, and different failure modes. The cash garment has capacity 1.0 by construction: it is always there. The options garment's capacity is priced by the market, and the market reprices insurance most expensively exactly when it is most needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What the market says protection costs
&lt;/h2&gt;

&lt;p&gt;The survey numbers are consistent across practitioners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Put insurance&lt;/strong&gt; (10-20% out-of-the-money, rolled quarterly): roughly 1-3% of portfolio per year. Deep OTM puts (20%+ strikes) cost 1-2% per quarter of protection; put spreads 0.5-1.5%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crisis repricing&lt;/strong&gt;: the same contract costs 2-5x in panicked markets. In March 2020 the VIX went from ~14 to 82; the S&amp;amp;P 500 fell 33.9% in 23 trading days, and puts that cost 1% in calm conditions were quoted at 5-10%. One example in the literature: a deep OTM put at normal IV 30% cost 0.10; at crisis IV 150% the same strike cost 4.50 — a 4,400% repricing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cash buffer&lt;/strong&gt;: the opportunity cost of holding cash is roughly 0.8-1.2% per year for moderate over-allocation, and it compounds brutally over decades.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The uncomfortable structure, and the reason this part exists: &lt;strong&gt;the retail investor's insurance has its own "pushing on a string" problem&lt;/strong&gt; — the exact phenomenon Tenreyro and Thwaites documented for monetary policy. Policy is weakest in the state where it is most needed. The retail put is most expensive in the state where it is most needed. Renewal is the clock.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The model: two garments, two geometries
&lt;/h2&gt;

&lt;p&gt;We reuse the exact market-loss machinery of Parts 15-18 (P4's &lt;code&gt;run_policy_moral_hazard&lt;/code&gt;, P6's capacity levels C) and overlay the two garments on the raw per-path losses, seed 20260921, 2,000 paths per level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bare&lt;/strong&gt; — no garment: loss = market loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cash&lt;/strong&gt; — 20% buffer: every path scaled by 0.8. Deterministic, always works; cost is the calm-market opportunity cost, 2.0%/yr at a 10% equity return assumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;put&lt;/strong&gt; — deductible 20%, premium 2%/yr: every path beyond -20% gets paid dollar-for-dollar above the deductible; loss is capped at -(20% + 2%) = -22%. The garment is a hard tail truncation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;put_crisis&lt;/strong&gt; — same deductible, but the premium is repriced at 6%/yr (2-3x, the survey's crisis renewal): what happens if you buy protection after the vol spike, not before.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;garment&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;worst&lt;/th&gt;
&lt;th&gt;&amp;gt;30% paths&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-24.6%&lt;/td&gt;
&lt;td&gt;-29.4%&lt;/td&gt;
&lt;td&gt;-31.5%&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;cash&lt;/td&gt;
&lt;td&gt;-19.7%&lt;/td&gt;
&lt;td&gt;-23.5%&lt;/td&gt;
&lt;td&gt;-25.2%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;put&lt;/td&gt;
&lt;td&gt;-21.5%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;put_crisis&lt;/td&gt;
&lt;td&gt;-25.5%&lt;/td&gt;
&lt;td&gt;-26.0%&lt;/td&gt;
&lt;td&gt;-26.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-30.7%&lt;/td&gt;
&lt;td&gt;-55.9%&lt;/td&gt;
&lt;td&gt;-61.6%&lt;/td&gt;
&lt;td&gt;27.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;cash&lt;/td&gt;
&lt;td&gt;-24.5%&lt;/td&gt;
&lt;td&gt;-44.7%&lt;/td&gt;
&lt;td&gt;-49.3%&lt;/td&gt;
&lt;td&gt;23.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;put&lt;/td&gt;
&lt;td&gt;-21.6%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-38.6%&lt;/td&gt;
&lt;td&gt;-61.3%&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;54.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;cash&lt;/td&gt;
&lt;td&gt;-30.8%&lt;/td&gt;
&lt;td&gt;-49.0%&lt;/td&gt;
&lt;td&gt;-52.9%&lt;/td&gt;
&lt;td&gt;49.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;put&lt;/td&gt;
&lt;td&gt;-21.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-47.1%&lt;/td&gt;
&lt;td&gt;-63.7%&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;80.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;cash&lt;/td&gt;
&lt;td&gt;-37.6%&lt;/td&gt;
&lt;td&gt;-51.0%&lt;/td&gt;
&lt;td&gt;-52.9%&lt;/td&gt;
&lt;td&gt;73.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;put&lt;/td&gt;
&lt;td&gt;-21.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;bare&lt;/td&gt;
&lt;td&gt;-33.9%&lt;/td&gt;
&lt;td&gt;-44.1%&lt;/td&gt;
&lt;td&gt;-46.7%&lt;/td&gt;
&lt;td&gt;84.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;cash&lt;/td&gt;
&lt;td&gt;-27.1%&lt;/td&gt;
&lt;td&gt;-35.3%&lt;/td&gt;
&lt;td&gt;-37.3%&lt;/td&gt;
&lt;td&gt;38.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;put&lt;/td&gt;
&lt;td&gt;-20.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-22.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-22.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The geometries are the whole point. The cash garment is a &lt;strong&gt;scaling&lt;/strong&gt;: every loss, shallow and deep, is multiplied by 0.8. The put garment is a &lt;strong&gt;truncation&lt;/strong&gt;: no path can lose more than 22%, and shallow paths are barely touched. Both cost 2% per year. Their difference only shows in the tail — which is exactly the region the previous three parts said you should be reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The clock reads in the renewal
&lt;/h2&gt;

&lt;p&gt;Tail protection per 1% of cost (p1 improvement over bare):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;cash saves&lt;/th&gt;
&lt;th&gt;put saves&lt;/th&gt;
&lt;th&gt;crisis-put saves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;5.9 pts&lt;/td&gt;
&lt;td&gt;7.4 pts&lt;/td&gt;
&lt;td&gt;3.4 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;11.2 pts&lt;/td&gt;
&lt;td&gt;33.9 pts&lt;/td&gt;
&lt;td&gt;29.9 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;12.3 pts&lt;/td&gt;
&lt;td&gt;39.3 pts&lt;/td&gt;
&lt;td&gt;35.3 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;12.7 pts&lt;/td&gt;
&lt;td&gt;41.7 pts&lt;/td&gt;
&lt;td&gt;37.7 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;8.8 pts&lt;/td&gt;
&lt;td&gt;22.1 pts&lt;/td&gt;
&lt;td&gt;18.1 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two findings, both in the real numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: the options garment dominates the cash garment in the tail, and the gap widens as policy capacity drains.&lt;/strong&gt; At C = 1.00, put and cash are close (7.4 vs 5.9 points saved at the 1st percentile). At C = 0.25, put saves 41.7 points against cash's 12.7 — the same 2% cost buys 3.3x more tail protection when the backstop is depleted. The reason is the truncation geometry: cash scales a -63.7% tail to -51.0%, put pins it at -22.0%. When the market's own backstop is probabilistic (Part 18), the private garment with a hard cap is the one that actually guarantees a floor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, and more important: the options garment's clock is the renewal, and it is exactly the Part 18 structure in miniature.&lt;/strong&gt; Locked at 2%, the put improves both mean and p1 at every capacity level. Repriced at 6% — the survey's crisis renewal, 2-3x — the same garment &lt;strong&gt;drags the mean below bare&lt;/strong&gt; in the calm world: at C = 1.00, mean worsens from -21.5% (locked) to -25.5% (renewed), worse than bare's -24.6%. The 6% premium now exceeds the average payout. In the depleted world the renewed put still rescues (-25.9% vs bare -47.1%), but the insurance has quietly become much more expensive exactly at the moment it matters most.&lt;/p&gt;

&lt;p&gt;This is the retail version of "pushing on a string": the policy clock of Part 18 — policy is weakest when most needed — has a private-market twin. Your protection is repriced by the same crisis that makes you need it. The put you roll in a panic costs 3x the put you bought in calm. The cash garment has no such repricing: it is the only asset class with capacity 1.0 by construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What the numbers say a retail investor should do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Wear both, in layers, and buy the options when they are cheap.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cash garment is the foundation. It is deterministic, it never fails, its failure mode is behavioral (spending it, panic-deploying it) rather than priced. At every capacity level it improves both mean and tail for 2% a year. There is no market state in which cash is not there.&lt;/p&gt;

&lt;p&gt;The options garment is the tail floor, and its purchase timing is the whole game. The 2% locked put and the 6% crisis put have identical payouts and wildly different economics: one improves the mean at every C, the other drags the mean in calm worlds. The difference is not the instrument — it is when you bought it. The rule writes itself: &lt;strong&gt;buy the umbrella in the sun&lt;/strong&gt;. Implied volatility is the price of protection, and implied volatility is mean-reverting and crisis-spiking. If you wait for the crisis to buy insurance, you are buying the most expensive insurance at the moment the tail is most likely.&lt;/p&gt;

&lt;p&gt;And the Part 18 link makes the timing concrete. Policy capacity is the clock: rate paths, balance-sheet run-off, debt trajectories, central-bank independence. As the clock reads down (C falling), the value of the options garment rises — 7.4 points saved at C = 1.00, 41.7 at C = 0.25. The correct retail behavior is not to predict the crisis; it is to hold the deterministic layer always, and to buy the probabilistic layer while the implied-volatility price still reflects a world in which the backstop looks intact. The market prices the promise. You price the capacity. The clock is read in the tail, and the garment is bought before the clock reads.&lt;/p&gt;

&lt;p&gt;Cost-wise, both garments run about 2% of portfolio per year. That is the price of the underwear: a deliberate, recurring, small premium against the region of the distribution where the previous three parts live. The alternative — no garment — is the bare column of the table above.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Where this leaves the series
&lt;/h2&gt;

&lt;p&gt;Part 18 priced the backstop's clock. Part 19 prices the retail garment that has to exist when the backstop is probabilistic: cash is the deterministic layer (capacity 1.0, always there, bought with opportunity cost), options are the probabilistic layer (hard tail floor, bought cheap or not at all, repriced by the same crisis that creates the need). The two garments cost the same and protect differently; the options garment's clock is its renewal, and the correct response to that clock is to buy protection when its price still assumes the backstop is intact.&lt;/p&gt;

&lt;p&gt;The remaining question, and the natural next part: the cash garment's failure mode is behavioral — panic, spending, redeployment at the worst moment. If the numbers say wear both, the discipline of wearing them is a behavioral problem, not a modeling one. That is Part 20.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/retail_underwear.py&lt;/code&gt; (V10-P7), self-test and Monte Carlo included. Deterministic, stdlib only, 256 lines. Numbers above are exact output with seed 20260921, market losses from the P4/P6 machinery.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Survey anchors: protective-put cost structures (1-3%/yr for 10-20% OTM, rolled; 2-5x repricing in crisis); March 2020 (VIX 14-&amp;gt;82, S&amp;amp;P -33.9% in 23 days, protection 1% -&amp;gt; 5-10%); cash opportunity cost (0.8-1.2%/yr moderate over-allocation); and the series' own Parts 15-18 modules and seed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Available for freelance work — Python pipelines, quantitative risk tooling, AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@winstonchen?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Winston Chen&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-full-moon-is-reflected-in-the-water-_OOQ-YbLdfw?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>The Backstop's Clock: Policy Space Is a Depleting Resource</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Sat, 26 Sep 2026 09:41:41 +0000</pubDate>
      <link>https://dev.to/fengyugbt/the-backstops-clock-policy-space-is-a-depleting-resource-36kd</link>
      <guid>https://dev.to/fengyugbt/the-backstops-clock-policy-space-is-a-depleting-resource-36kd</guid>
      <description>&lt;p&gt;&lt;em&gt;Fat Tail Notes · Part 18 · V10-P6&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Part 12 priced the twenty-day window — the clock that runs inside a crisis, from the first margin call to the backstop's arrival. Today we price the other clock: the one that runs &lt;em&gt;across&lt;/em&gt; crises, on the policy space itself. In July 2007 the Federal Reserve had 5.25 percentage points of interest-rate room. In December 2008 it had a target band of zero to 25 basis points, and it stayed there for seven years. Policy space is not infinite. It is a depleting resource, and our simulator says the market starts pricing the depletion in the tail long before the headline numbers move.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The evidence that policy space is a depleting resource
&lt;/h2&gt;

&lt;p&gt;Three literatures, again none of them ours, again all pointing the same way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monetary space hits a floor.&lt;/strong&gt; Eggertsson and Woodford's &lt;em&gt;Zero Bound on Interest Rates and Optimal Monetary Policy&lt;/em&gt; (2003, one of the most cited papers in this literature) formalized what Japan had already shown: at the zero lower bound, the usual remedy — lower short rates — is unavailable, and unconventional policy works only through expectations. Woodford's 2012 survey of lower-bound accommodation is the practical sequel: once the rate is pinned, every additional unit of stimulus has to be borrowed from credibility, forward guidance, and balance-sheet size. The Fed's own history is the cleanest example: 5.25% in mid-2007, zero-to-25bp by December 2008, and no meaningful headroom for a decade. The resource was spent, not just used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The effect weakens exactly when it is most needed.&lt;/strong&gt; Tenreyro and Thwaites' &lt;em&gt;Pushing on a String&lt;/em&gt; finds that US monetary policy is systematically less powerful in recessions than in expansions — the elasticity of the response to a policy shock falls in the state where stimulus matters most. This is the "pushing on a string" intuition with data behind it, and it has a long pedigree: after the Great Depression, mainstream economics spent decades assuming easy money was powerless against a slump. If policy is weaker in the very state where it is deployed, then the same policy space buys less rescue per unit — a second kind of depletion on top of the first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fiscal space is the insurance, and it can run out.&lt;/strong&gt; Romer and Romer's &lt;em&gt;Fiscal Space and the Aftermath of Financial Crises&lt;/em&gt; (Brookings Papers, 2019) studies 30 countries over 1980–2017: countries that entered financial distress with lower debt-to-GDP ratios responded with far more expansionary fiscal policy and suffered far milder aftermaths. Their message is almost a policy version of our leverage subsidy: maintaining fiscal space in normal times is the insurance that makes crisis response possible. Kose, Kurlat, Ohnsorge and Sugawara's cross-country fiscal space database (up to 200 countries, 1990–2016) shows the resource is real, measurable, and depleted in crises. And Salamaliki and Venetis document that market participants' concern about fiscal space is &lt;strong&gt;nonlinear&lt;/strong&gt; — the market does not price the loss of the first unit of space like the last.&lt;/p&gt;

&lt;p&gt;All three say the same structural thing: the backstop has ammunition, ammunition is finite, and the market knows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The model: leverage is deterministic, protection is probabilistic
&lt;/h2&gt;

&lt;p&gt;Parts 15–17 treated market leverage λ as an exogenous distribution — the world the backstop creates. Part 18 makes it endogenous to policy capacity C. The backstop's capacity C ∈ [0,1] maps to two things: the probability the backstop actually fires on a given path, and the leverage the market books against that promise.&lt;/p&gt;

&lt;p&gt;The structure is the whole point: &lt;strong&gt;leverage is booked deterministically on the promise; protection is delivered probabilistically from capacity.&lt;/strong&gt; As C falls, the same high-leverage book faces a smaller chance of rescue. We call the paths where leverage is held but protection never arrives &lt;em&gt;betrayed&lt;/em&gt; paths. All numbers below are exact output of &lt;code&gt;policy_space.py&lt;/code&gt; (V10-P6), seed 20260921, 2,000 paths per level.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;trigger&lt;/th&gt;
&lt;th&gt;market λ&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;p10&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;worst&lt;/th&gt;
&lt;th&gt;&amp;gt;30% paths&lt;/th&gt;
&lt;th&gt;betrayed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;~N(1.35,0.25)&lt;/td&gt;
&lt;td&gt;-24.6%&lt;/td&gt;
&lt;td&gt;-27.6%&lt;/td&gt;
&lt;td&gt;-29.4%&lt;/td&gt;
&lt;td&gt;-31.5%&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;~N(1.50,0.30)&lt;/td&gt;
&lt;td&gt;-30.7%&lt;/td&gt;
&lt;td&gt;-47.8%&lt;/td&gt;
&lt;td&gt;-55.9%&lt;/td&gt;
&lt;td&gt;-61.6%&lt;/td&gt;
&lt;td&gt;27.2%&lt;/td&gt;
&lt;td&gt;25.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;~N(1.75,0.35)&lt;/td&gt;
&lt;td&gt;-38.6%&lt;/td&gt;
&lt;td&gt;-55.4%&lt;/td&gt;
&lt;td&gt;-61.3%&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;54.9%&lt;/td&gt;
&lt;td&gt;50.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;~N(2.00,0.40)&lt;/td&gt;
&lt;td&gt;-47.1%&lt;/td&gt;
&lt;td&gt;-60.5%&lt;/td&gt;
&lt;td&gt;-63.7%&lt;/td&gt;
&lt;td&gt;-66.1%&lt;/td&gt;
&lt;td&gt;80.5%&lt;/td&gt;
&lt;td&gt;75.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;λ=1.0 (no promise)&lt;/td&gt;
&lt;td&gt;-33.9%&lt;/td&gt;
&lt;td&gt;-41.0%&lt;/td&gt;
&lt;td&gt;-44.1%&lt;/td&gt;
&lt;td&gt;-46.7%&lt;/td&gt;
&lt;td&gt;84.4%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3. The clock reads in the tail first
&lt;/h2&gt;

&lt;p&gt;Compare each level to the full-capacity world (C = 1.00):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;mean drift&lt;/th&gt;
&lt;th&gt;p1 drift&lt;/th&gt;
&lt;th&gt;paths &amp;gt; 30%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;-6.1 pts&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-26.5 pts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.4% → 27.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;-14.0 pts&lt;/td&gt;
&lt;td&gt;-31.9 pts&lt;/td&gt;
&lt;td&gt;54.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;-22.5 pts&lt;/td&gt;
&lt;td&gt;-34.3 pts&lt;/td&gt;
&lt;td&gt;80.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mean is the last number to move. The first 25% of capacity loss costs 6.1 points of mean — and 26.5 points at the 1st percentile. By the time the headline has deteriorated by a dramatic-looking 22.5 points, the p1 has already moved 34.3 and four out of five paths are crossing the 30% cascade line. If you are watching the mean — the way press coverage, most risk models, and the Bornstein-Lorenzoni welfare function do — the backstop looks intact until it is catastrophic. The policy space clock is read from the tail; the headline is the lagging indicator.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The most dangerous policy space is believed but depleted
&lt;/h2&gt;

&lt;p&gt;Here is the finding that the model was not designed to produce. Look at the last two rows: C = 0.25 is &lt;strong&gt;worse&lt;/strong&gt; than C = 0. The fully-depleted, no-promise world has p1 of -44.1% and mean of -33.9%. The quarter-capacity world — where the market still believes in a backstop that fires one time in four — has p1 of -63.7% and mean of -47.1%.&lt;/p&gt;

&lt;p&gt;Why? Because in the C = 0 world, no one books leverage on a promise that does not exist. Leverage returns to its exogenous 1.0, and the loss distribution is the honest no-backstop one. In the C = 0.25 world, the promise still exists in the market's head: leverage is booked at λ ~ 2.0 — &lt;em&gt;higher&lt;/em&gt; than in the full-capacity world, because the market front-runs the scarcity — and then protection arrives on only 25% of paths. 75% of the time, the book is betrayed: levered for a backstop that does not come.&lt;/p&gt;

&lt;p&gt;This is the policy analogue of a margin spiral: &lt;strong&gt;credibility is the collateral, and the market prices it.&lt;/strong&gt; The resource is not just depleted; it is dangerous precisely because it remains &lt;em&gt;believed&lt;/em&gt; while depleted. There is a discontinuity — a credibility cliff — between "the backstop is weak" and "the backstop is gone." Between those two points, the market books maximum leverage against minimum protection.&lt;/p&gt;

&lt;p&gt;The real-world mapping is uncomfortable. The Fed's balance sheet, the fiscal headroom in a debt-to-GDP ratio, the central bank's independence, the political will to let a lender of last resort be a lender of &lt;em&gt;unlimited&lt;/em&gt; resort — all of these are capacity variables, and all of them can sit at values where the market still believes but the capacity is a fraction of what the belief assumes. The worst policy space is not the empty one. It is the one that is half-believed.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What this means for a retail investor
&lt;/h2&gt;

&lt;p&gt;Three rules again, none requiring a forecast.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;"the backstop exists" and "the backstop is credible" are different variables, and only the second one matters.&lt;/strong&gt; When a central bank says it has tools, the market does not price the tools; it prices the difference between the promise and the capacity. The news cycle gives you the promise. The tail prices the capacity. Your job is to notice when the two start to diverge.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;policy space is a leading indicator that lives in boring places.&lt;/strong&gt; Interest-rate paths, balance-sheet run-off schedules, debt-to-GDP trajectories, debt-ceiling standoffs, central-bank independence fights — each of these is a reading of the clock. You do not need to model the Fed. You need to know whether the resource the market is levering against is rising or falling, because the tail prices the second derivative long before the headline prints the first.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;the most dangerous moment is not "the backstop failed" — it is "the backstop is expected and weak."&lt;/strong&gt; Our table says the leverage cycle peaks in the depleted-but-believed zone. That is the moment to ask what your own book looks like if protection arrives with probability 25%: not "will the Fed act?" but "is my position priced as if the Fed certainly acts?" The first question is about the news. The second is about you.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Where this leaves the series
&lt;/h2&gt;

&lt;p&gt;Part 12 gave the twenty-day window a clock. Part 18 gives the backstop itself a clock, and the clock reads in the tail: mean first to look fine, first to be believed, last to warn. The three literatures — ZLB, pushing on a string, fiscal space — and our simulator agree: policy space is a depleting resource, its price is nonlinear, and the market books leverage against the promise while protection is only as real as the capacity behind it.&lt;/p&gt;

&lt;p&gt;The uncomfortable next question, and the one this series has been walking toward since Part 11: if the backstop has a clock, and the clock is read in the tail, what does the retail investor's own book look like when protection is probabilistic? The underwear we keep saying retail investors need — what does it actually consist of, and what does it cost? That is Part 19.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/policy_space.py&lt;/code&gt; (V10-P6), self-test and Monte Carlo included. Deterministic, stdlib only, 212 lines. Numbers above are exact output with seed 20260921.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Papers: Eggertsson &amp;amp; Woodford, "The Zero Bound on Interest Rates and Optimal Monetary Policy" (2003); Woodford, "Methods of Policy Accommodation at the Interest-Rate Lower Bound" (2012); Tenreyro &amp;amp; Thwaites, "Pushing on a String" (2016); Romer &amp;amp; Romer, "Fiscal Space and the Aftermath of Financial Crises" (BPEA, 2019); Kose, Kurlat, Ohnsorge &amp;amp; Sugawara, "A Cross-Country Database of Fiscal Space"; Salamaliki &amp;amp; Venetis (2023); plus the series' own P4/P5 modules and seed 20260921 Monte Carlo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Available for freelance work — Python pipelines, quantitative risk tooling, AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@zoltantasi?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Zoltan Tasi&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/grayscale-photo-of-sea-waves-crashing-on-concrete-wall-bjjTsPATZ_w?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Expensive Sixth Point: Convexity, Thresholds, and the Price of Tail Risk</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Fri, 25 Sep 2026 04:34:17 +0000</pubDate>
      <link>https://dev.to/fengyugbt/the-expensive-sixth-point-convexity-thresholds-and-the-price-of-tail-risk-4dch</link>
      <guid>https://dev.to/fengyugbt/the-expensive-sixth-point-convexity-thresholds-and-the-price-of-tail-risk-4dch</guid>
      <description>&lt;p&gt;&lt;em&gt;Fat Tail Notes · Part 17 · V10-P5&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last week we argued that the disagreement over the Greenspan put is a measurement problem: expected welfare has no tail, and tail risk has no average. This week we price the tail properly, and the price turns out to be worse than the mean tells you. A -25% drawdown and a -31% drawdown differ by six points. Their social costs do not differ by six points. The sixth point is the expensive one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The evidence that crisis costs are convex
&lt;/h2&gt;

&lt;p&gt;Before our own numbers, three independent literatures, none of them ours, all say the same thing: the damage function is not linear in the size of the crisis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistence.&lt;/strong&gt; Cerra and Saxena's &lt;em&gt;Growth Dynamics: The Myth of Economic Recovery&lt;/em&gt; (2008) is the canonical statement: contractions are not followed by offsetting fast recoveries. On average, the trend output lost is never regained; the output costs of financial crises are permanent. A recent study of 180 economic disasters across 38 countries (Ori &amp;amp; Peri, 2023) is more specific: output loss surges past 26% in the first years after an extreme crisis and stays above 20% for as long as twenty years; full recovery takes over fifty years. Boyd, Kwak and Smith (2005) compute present values: average crisis-related losses are between 63% and 302% of pre-crisis per-capita GDP, because post-crisis slowdowns persist long after the crisis is "officially" over. A 25% drawdown that recovers is a cost. A 31% drawdown that does not recover is a different species of cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heavy-tailed severity.&lt;/strong&gt; Laeven and Valencia's systemic banking crises database (151 episodes, 1970–2017) is the standard distribution of crisis outcomes, and it is not a thin-tailed one. Kapp and Vega take the logical next step for our purposes: they apply the insurance industry's Loss Distribution Approach — frequency times severity, exactly the catastrophe-modeling machinery this series started from — to financial crises, and estimate a multi-country GDP loss distribution. The catastrophe framework is not an analogy we borrowed to look sophisticated. It is the correct tool, because crisis losses behave like disaster losses: rare, severe, and heavy-tailed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Political aftershocks.&lt;/strong&gt; Funke, Schularick and Trebesch (&lt;em&gt;Going to Extremes: Politics after Financial Crises, 1870–2014&lt;/em&gt;) study 800+ elections across 20 advanced economies. After a financial crisis, far-right parties gain, on average, 30% in vote share — and critically, they find &lt;em&gt;no such dynamics in normal recessions or non-financial macroeconomic shocks&lt;/em&gt;. Mian, Sufi and Trebbi document the mechanism: crises polarize voters, weaken ruling coalitions, and reduce the odds of exactly the financial reforms that would help. The tail event does not just destroy wealth; it changes the political system that decides how the losses are distributed. No linear cost function contains that.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Three lenses on the same Monte Carlo table
&lt;/h2&gt;

&lt;p&gt;Our simulator draws 2,000 shocks, seed 20260921, and runs them through two worlds: exogenous leverage (λ = 1.0) and policy-endogenous leverage (λ drawn above 1 — the world the backstop creates). Last week we published the mean, p10, and p1 columns. This week we add two cost columns: a power cost C(L) = (−L)², and a threshold cost that adds a fixed jump once the drawdown crosses 30% — the regime where cascades, dealer gamma flips, liquidity gaps, and political aftershocks begin. All numbers below are exact output of &lt;code&gt;crisis_cost_nonlinearity.py&lt;/code&gt; (V10-P5), same seed, same 2,000 paths as Part 15 and 16.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;scenario&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;p10&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;E[C²]&lt;/th&gt;
&lt;th&gt;E[C_thr]&lt;/th&gt;
&lt;th&gt;paths &amp;gt; 30%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no backstop, λ=1.0&lt;/td&gt;
&lt;td&gt;-34.1%&lt;/td&gt;
&lt;td&gt;-41.0%&lt;/td&gt;
&lt;td&gt;-43.9%&lt;/td&gt;
&lt;td&gt;0.1245&lt;/td&gt;
&lt;td&gt;0.7701&lt;/td&gt;
&lt;td&gt;85.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ=1.0&lt;/td&gt;
&lt;td&gt;-21.3%&lt;/td&gt;
&lt;td&gt;-23.2%&lt;/td&gt;
&lt;td&gt;-24.8%&lt;/td&gt;
&lt;td&gt;0.0473&lt;/td&gt;
&lt;td&gt;0.2134&lt;/td&gt;
&lt;td&gt;0.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ~N(1.35,0.25)&lt;/td&gt;
&lt;td&gt;-24.5%&lt;/td&gt;
&lt;td&gt;-27.5%&lt;/td&gt;
&lt;td&gt;-29.4%&lt;/td&gt;
&lt;td&gt;0.0616&lt;/td&gt;
&lt;td&gt;0.2466&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ~N(1.5,0.30)&lt;/td&gt;
&lt;td&gt;-25.5%&lt;/td&gt;
&lt;td&gt;-28.4%&lt;/td&gt;
&lt;td&gt;-30.4%&lt;/td&gt;
&lt;td&gt;0.0663&lt;/td&gt;
&lt;td&gt;0.2632&lt;/td&gt;
&lt;td&gt;1.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ~N(1.75,0.35)&lt;/td&gt;
&lt;td&gt;-26.9%&lt;/td&gt;
&lt;td&gt;-29.5%&lt;/td&gt;
&lt;td&gt;-31.1%&lt;/td&gt;
&lt;td&gt;0.0732&lt;/td&gt;
&lt;td&gt;0.2973&lt;/td&gt;
&lt;td&gt;5.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Lens one — the mean.&lt;/strong&gt; The backstop rescues the mean by 12.8 points (-34.1% → -21.3%). Endogenous leverage eats 5.6 of those back (-21.3% → -26.9%), 44% of the rescue. By this lens, the policy is doing its job, with some slippage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lens two — convex cost.&lt;/strong&gt; Under C(L) = (−L)², the same rescue is worth 0.077 cost units (0.1245 → 0.0473); endogenous leverage eats 0.026, 34%. Notice something important: the &lt;em&gt;share&lt;/em&gt; eaten is smaller than under the mean. That is Jensen's inequality working for the policy for once — the rescue is concentrated where the squared cost is largest, so the convexity magnifies the rescue as well as the erosion. The honest reading is not "convexity makes moral hazard smaller"; it is that both the value of the policy and the cost of the leverage it induces live in the tail, and the mean can only report their average.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lens three — the threshold.&lt;/strong&gt; This is the uncomfortable one. The exogenous backstop puts the 1st percentile at -24.8%, safely inside the 30% line; only 0.1% of paths cross it. Let leverage become endogenous at λ~N(1.75, 0.35) and the p1 moves to -31.1% — &lt;strong&gt;across the threshold&lt;/strong&gt; — and the share of paths in the cascade zone rises 57-fold, from 0.1% to 5.7%. Expected threshold cost rises from 0.2134 to 0.2973, a 39% increase in the cost the policy exists to prevent.&lt;/p&gt;

&lt;p&gt;The mean does not see this. In mean terms, the λ~1.75 world sits at -26.9%, still 7.2 points away from the no-backstop world — the rescue, by that measure, still looks structurally intact. The tail tells a different story: the worst 1% of outcomes has already crossed the line the policy promised to defend. Six points of mean slippage look like a second-order correction. Six points at the 1st percentile are a first-order regime change.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The patient, again, with better measurements
&lt;/h2&gt;

&lt;p&gt;Last week's doctor analogy: the painkiller lowers mean pain from 7 to 4 but worsens the worst day from 9 to 10. This week we add the detail the doctor was missing. The question is not just "what is the worst day now?" It is "what happens when a patient crosses the line where the injury stops healing?" A 9 and a 10 on the pain scale are one point apart. But if 10 is the day the patient's joint gives out, the difference is not one point of pain — it is a joint that no longer works. That is what a threshold does: it converts a small difference on the measurement scale into a categorical difference in the outcome.&lt;/p&gt;

&lt;p&gt;The leverage the backstop induces is a subsidy to stand closer to the threshold. Most of the time it looks harmless — that is what 0.1% vs 5.7% means from the outside: rare. But "rare" is exactly the object this series has been modeling since Part 1, and the probability is not a constant; it is a function of the policy itself. The exogenous world put the worst percentile inside the line. The endogenous world puts it across.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What this means for a retail investor
&lt;/h2&gt;

&lt;p&gt;Three rules fall out of the threshold view, and none of them requires you to forecast the next crisis.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;price the tail with a convex function, not an average.&lt;/strong&gt; If you estimate your crisis risk by expected loss, you are using the lens that hides the threshold-crossing. The difference between "protect the p1" and "improve the mean" is not a nuance; it is the whole argument about what insurance is for. Retail investors rarely do the first. That is the gap this series keeps circling.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;the policy floor has a location, and it moves.&lt;/strong&gt; A backstop is not a flat guarantee; it is a line in the distribution, and the line's effective position depends on how much leverage it induces. When you hear "the Fed put is back," ask not whether it exists but &lt;em&gt;where&lt;/em&gt; it sits in the current book structure. The same policy that looked like a floor at -25% looks like a trap at -31%, because by then the cascade and the politics are already doing their work.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;"six more points" is the wrong mental unit.&lt;/strong&gt; The cost difference between -25% and -31% is not 6/25 of your loss — it is the probability-weighted cost of everything beyond the threshold: forced liquidations, gamma flips, policy-space exhaustion, and the political aftershocks that turn a market event into a regulatory one. The sixth point buys you across the line. That is why tail hedges that look expensive in calm markets are not overpriced; they are priced against the threshold, and the threshold is what the leverage cycle quietly moves toward you.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Where this leaves the series
&lt;/h2&gt;

&lt;p&gt;Part 16 said the moral-hazard debate is really a measurement debate. Part 17 says the measurement that matters is convex and thresholded, not linear and averaged. The academic literatures — persistence, severity distribution, political aftershock — are consistent with each other and with our simulator: the backstop's value and its cost are both in the tail, and the mean cannot arbitrate between them.&lt;/p&gt;

&lt;p&gt;A note on method: this series has now published numbers from four V10 modules (backstop, expectations, anticipation, moral hazard) and this one (convex cost), all deterministic, all self-tested, all reproducible with the same seed. The numbers in this post are the exact output of &lt;code&gt;crash_simulator_v10/crisis_cost_nonlinearity.py&lt;/code&gt; (V10-P5), 204 lines of dependency-free Python with a self-test, run with the series' standard seed. If you can run Python, you can check every number above in under a minute.&lt;/p&gt;

&lt;p&gt;Next: if the tail is priced by a threshold, the next question is what happens when the policy space itself is the constraint — the day the backstop cannot backstop. The twenty-day window had a clock. The backstop may have one too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/crisis_cost_nonlinearity.py&lt;/code&gt; (V10-P5), self-test and Monte Carlo included. Deterministic, stdlib only, 204 lines. Numbers above are exact output with seed 20260921.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Papers: Cerra &amp;amp; Saxena, "Growth Dynamics: The Myth of Economic Recovery" (2008); Ori &amp;amp; Peri, "Recovery from Economic Disasters" (2023); Boyd, Kwak &amp;amp; Smith (2005); Laeven &amp;amp; Valencia, "Systemic Banking Crises Database II" (IMF, 2020); Kapp &amp;amp; Vega, "Real Output Costs of Financial Crises: A Loss Distribution Approach"; Funke, Schularick &amp;amp; Trebesch, "Going to Extremes: Politics after Financial Crises, 1870–2014"; Mian, Sufi &amp;amp; Trebbi, "Resolving Debt Overhang" (2014).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Available for freelance work — Python pipelines, quantitative risk tooling, AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@nikolayh?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Nikolay Hristov&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/storm-on-sea-9DBQ4D_LwNk?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantitative</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Moral Hazard Is Not a Misconception — It's a Measurement Problem</title>
      <dc:creator>Feng Yu</dc:creator>
      <pubDate>Thu, 24 Sep 2026 11:29:22 +0000</pubDate>
      <link>https://dev.to/fengyugbt/moral-hazard-is-not-a-misconception-its-a-measurement-problem-en6</link>
      <guid>https://dev.to/fengyugbt/moral-hazard-is-not-a-misconception-its-a-measurement-problem-en6</guid>
      <description>&lt;p&gt;&lt;em&gt;Fat Tail Notes · Part 16 · V10-P4 revisited&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last week this series argued that a backstop builds the tail it truncates: once leveraged accounts believe the policy will cap their losses, leverage rises, and the same shock lands on bigger books. A real academic paper is now arguing the opposite — and it is not a blog post, it is IMF-published research. Gideon Bornstein and Guido Lorenzoni's &lt;em&gt;Moral Hazard Misconceptions: The Case of the Greenspan Put&lt;/em&gt; (2018) makes the case that the fear of the Fed put is, in their words, a misconception: under optimal discretionary intervention, borrowing rises but overborrowing disappears, and ex-post intervention becomes a substitute for ex-ante regulation.&lt;/p&gt;

&lt;p&gt;My first reaction after reading it was to concede. My second reaction was that this is not a dispute about who is right. It is a dispute about which moment of the loss distribution you are willing to optimize. And the third, most uncomfortable finding: our own Part 15 contained a data error in two of its mean figures. This post fixes that too, because if you want readers to trust your tail numbers, you correct your center numbers in public.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What the paper actually says
&lt;/h2&gt;

&lt;p&gt;The model is a three-period economy with a levered agent (B) who holds the risky asset, financed by debt from a patient saver (A), with sticky prices and an aggregate-demand externality. The mechanism runs through the labor wedge: a larger debt stock worsens recessions because debt payments transfer resources from high-propensity borrowers to low-propensity lenders, dragging down output exactly when output is already too low.&lt;/p&gt;

&lt;p&gt;The paper compares three monetary regimes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inertial&lt;/strong&gt;: the central bank sets the interest rate before seeing the shock. The shock is uninsured, the aggregate-demand externality is live, and there is &lt;em&gt;overborrowing&lt;/em&gt; — a marginal welfare loss from debt (dW/dD &amp;lt; 0). This is the textbook case for macroprudential regulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive&lt;/strong&gt;: the central bank sets rates state-by-state after seeing the shock. With log preferences, it can fully stabilize asset prices and output — the Greenspan put at maximum potency. Borrowing goes &lt;em&gt;up&lt;/em&gt; (Proposition 2), but overborrowing goes to zero (Proposition 3): dW/dD = 0, so a borrowing tax buys nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output targeting&lt;/strong&gt;: commits to the flexible-price allocation; coincides with the proactive regime in the log case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The headline: more borrowing, less overborrowing. The put does not create the inefficiency that regulation exists to fix, so ex-post intervention and ex-ante macroprudential policy are &lt;em&gt;substitutes&lt;/em&gt;. Moral hazard as conventionally feared is a misconception.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The knife-edge they concede
&lt;/h2&gt;

&lt;p&gt;The full-results version is narrower than the headline, and the paper says so itself. The perfect-stabilization result is a knife-edge that requires log utility — an elasticity of intertemporal substitution of exactly one, where income and substitution effects cancel. Section 5 shows what happens away from that edge: once preferences are general CRRA, the central bank faces a tradeoff between output stabilization and financial stability, and the insurance motive pushes it to stabilize asset prices &lt;em&gt;beyond&lt;/em&gt; what output targeting requires.&lt;/p&gt;

&lt;p&gt;That is the telling part. &lt;strong&gt;In their own Section 5.2, the moment the central bank goes beyond output-gap targeting to prop up asset prices — the actual behavior people mean by "the put" — monetary policy and macroprudential policy become complements, and the optimal borrowing tax is &lt;em&gt;larger&lt;/em&gt; in the proactive regime than in the output-targeting regime.&lt;/strong&gt; The conventional moral-hazard result comes back. What the paper really shows is not "moral hazard is a misconception" but "moral hazard disappears exactly on the log-utility knife-edge where the put is a perfect insurance contract."&lt;/p&gt;

&lt;p&gt;There is a second, quieter assumption running through the whole paper: welfare is always evaluated as &lt;em&gt;expected&lt;/em&gt; utility, E[V^A + βV^B]. Every proposition is about the mean of the welfare distribution. Nothing in the model — not one equation — looks at the lower tail of the outcome distribution as a separate object.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What our code says, on both accounts
&lt;/h2&gt;

&lt;p&gt;Here is the trap we built to show. Our Monte Carlo (2,000 paths, seed 20260921) lets leverage be either exogenous (λ = 1.0) or policy-endogenous (λ drawn from a distribution with mean above 1 — the world the backstop creates). The same 2,000 shocks, two different worlds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;scenario&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;p10&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;worst&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no backstop, λ=1.0 (exogenous)&lt;/td&gt;
&lt;td&gt;-34.1%&lt;/td&gt;
&lt;td&gt;-41.0%&lt;/td&gt;
&lt;td&gt;-43.9%&lt;/td&gt;
&lt;td&gt;-49.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ=1.0 (exogenous)&lt;/td&gt;
&lt;td&gt;-21.3%&lt;/td&gt;
&lt;td&gt;-23.2%&lt;/td&gt;
&lt;td&gt;-24.8%&lt;/td&gt;
&lt;td&gt;-31.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ ~ N(1.35, 0.25)&lt;/td&gt;
&lt;td&gt;-24.5%&lt;/td&gt;
&lt;td&gt;-27.5%&lt;/td&gt;
&lt;td&gt;-29.4%&lt;/td&gt;
&lt;td&gt;-31.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ ~ N(1.5, 0.30)&lt;/td&gt;
&lt;td&gt;-25.5%&lt;/td&gt;
&lt;td&gt;-28.4%&lt;/td&gt;
&lt;td&gt;-30.4%&lt;/td&gt;
&lt;td&gt;-31.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backstop, λ ~ N(1.75, 0.35)&lt;/td&gt;
&lt;td&gt;-26.9%&lt;/td&gt;
&lt;td&gt;-29.5%&lt;/td&gt;
&lt;td&gt;-31.1%&lt;/td&gt;
&lt;td&gt;-33.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now read the same table through each paper's lens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bornstein-Lorenzoni's lens — the mean.&lt;/strong&gt; The backstop rescues the mean by 12.8 points (-34.1% → -21.3%). Endogenous leverage eats 5.6 of those points back (-21.3% → -26.9% at λ~1.75), 44% of the mean rescue. By a mean-welfare standard, the policy is doing its job: it improves the average outcome massively, and the erosion, while real, is a second-order correction to a first-order rescue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our lens — the tail.&lt;/strong&gt; The backstop's p1 rescue is 19.1 points (-43.9% → -24.8%) — the tail is where the policy's value is concentrated. Endogenous leverage eats 6.3 points of that tail (-24.8% → -31.1%), 33% of the p1 rescue, and shifts p10 from -23.2% to -29.5%. The rescue is not canceled, but the deepest outcomes the policy promises to prevent are precisely the ones drifting back toward the no-policy world.&lt;/p&gt;

&lt;p&gt;Same shocks, same policy, two defensible accounts. The disagreement is not in the mechanism — both accounts agree leverage responds to the put. The disagreement is in the objective function: &lt;strong&gt;expected welfare has no tail. Tail risk has no average.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Which measure is right? It depends on the cost of the tail.
&lt;/h2&gt;

&lt;p&gt;Here is the honest version of the argument. If the social cost of a crisis is proportional to the mean loss, Bornstein and Lorenzoni are right — the backstop is a clean win and the moral-hazard complaint is a distraction. But crisis costs are not proportional. A -25% drawdown and a -31% drawdown do not cost society in the same units: the second one crosses into forced selling cascades, dealer gamma flips, liquidity gaps, policy-space exhaustion, and the political aftershocks that turn a market event into a regulatory one. The cost function is convex in the loss, and convexity is exactly what expected utility with representative agents averages away.&lt;/p&gt;

&lt;p&gt;Two doctors see the same patient. Doctor A prescribes the painkiller because mean pain over the year falls from 7 to 4. Doctor B objects that the worst day — the day the patient overexerts because the painkiller masks the injury — went from 9 to 10. Both are reporting the same data. The patient's choice is which number the treatment is supposed to optimize. A central bank that only reports the mean is Doctor A reporting only the mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Their paper actually licenses our setup
&lt;/h2&gt;

&lt;p&gt;The most useful sentence in &lt;em&gt;Moral Hazard Misconceptions&lt;/em&gt; is the one that limits its own headline: complementarity — the return of moral hazard — appears "if the monetary authority goes over and above a simple objective of reducing the output gap." Our Part 12-15 series models exactly that kind of intervention: a &lt;em&gt;backstop&lt;/em&gt;, an explicit asset-price floor and liquidity guarantee, not a Taylor rule. We are not on their log-utility knife-edge where the put is a perfect insurance contract; we are in the regime their Section 5.2 shows to be the complementary one, where the put stabilizes asset prices beyond output targeting and the borrowing tax is &lt;em&gt;more&lt;/em&gt; valuable, not less.&lt;/p&gt;

&lt;p&gt;Which is why the two literatures converge on the same policy conclusion from opposite directions. Boissay and Uhlig's &lt;em&gt;Reserves and the Buyer of Last Resort&lt;/em&gt; (NBER w35548) calls it the market-backstop principle: make the backstop state-contingent and pair it with liquidity requirements that tax the behavior the backstop insures. Our Part 15 ended at the same place by simulation: ambiguity stops the front-run, but only a leverage rule stops the subsidy.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Correction and judgment
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Correction.&lt;/strong&gt; In the Part 15 Monte Carlo table, two mean figures were wrong: the no-backstop mean should be -34.1% (published as -42.3%) and the exogenous-backstop mean should be -21.3% (published as -24.6%). All p10, p1, and worst figures were correct. The corrected means change one sentence of that post's argument: the mean does move under endogenous leverage (-21.3% → -24.5% → -25.5% → -26.9%), it just moves less than the tail, and the rescue's value is concentrated in the tail either way. The stronger, corrected claim: &lt;strong&gt;the backstop's value lives in the tail, and the tail is also where the leverage it induces does the most damage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Judgment.&lt;/strong&gt; For the retail investor, the academic argument over moral hazard is not the thing to resolve. Two facts survive it. First, in mean terms, interventions do hold the market up — the rescue is real, and betting against it is a fool's trade. Second, in tail terms, the same intervention is accumulating the leverage that will make the next floor deeper — the rescue is expensive, and it is priced in the distribution's tail, not its headline. A put is not permission to be naked; it is time to put on the underwear.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;crash_simulator_v10/policy_moral_hazard.py&lt;/code&gt; (V10-P4), self-test and Monte Carlo included. Deterministic, 402 lines, no external dependencies. Correction committed alongside this post.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Paper: Bornstein &amp;amp; Lorenzoni, "Moral Hazard Misconceptions: The Case of the Greenspan Put" — gideon-bornstein.com/papers/Moral_Hazard_Greenspan_Put.pdf&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Available for freelance work — Python pipelines, quantitative risk tooling, AI data automation. Reach me at &lt;a href="mailto:gopipibank@gmail.com"&gt;gopipibank@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance; facts, figures, and errors are the author's own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@iamthedave?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Dave Hoefler&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/green-grass-field-under-gray-clouds-IRHNxX5y_hg?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>quantitative</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
