<?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: Sudharshan TK</title>
    <description>The latest articles on DEV Community by Sudharshan TK (@shantk18).</description>
    <link>https://dev.to/shantk18</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%2F164343%2Fc886eadb-906c-4d23-a250-73fc5d3b295d.jpg</url>
      <title>DEV Community: Sudharshan TK</title>
      <link>https://dev.to/shantk18</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shantk18"/>
    <language>en</language>
    <item>
      <title>I Overengineered My Personal Finances into a Local-First Lakehouse (DuckDB + Polars + SQLite)</title>
      <dc:creator>Sudharshan TK</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:16:48 +0000</pubDate>
      <link>https://dev.to/shantk18/i-overengineered-my-personal-finances-into-a-local-first-lakehouse-duckdb-polars-sqlite-29nc</link>
      <guid>https://dev.to/shantk18/i-overengineered-my-personal-finances-into-a-local-first-lakehouse-duckdb-polars-sqlite-29nc</guid>
      <description>&lt;p&gt;By professional training, I am a &lt;strong&gt;Chartered Accountant based in India&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;By night and obsession, I fell completely down the rabbit hole of Python, data engineering, and quantitative modeling.&lt;/p&gt;

&lt;p&gt;That sounds like an unusual combination, but in practice, Chartered Accountancy and data engineering share almost the exact same DNA:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finance / Audit Instinct&lt;/th&gt;
&lt;th&gt;Engineering Analogue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evidence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Source lineage &amp;amp; cryptographic content hashes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reconciliation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data quality controls &amp;amp; invariant checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Classification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Canonical semantic modeling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accounting Period&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Temporal grain &amp;amp; windowing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audit Trail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Run manifests &amp;amp; provenance logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Financial Statements&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Serving contracts &amp;amp; dimensional data marts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Accounting makes you deeply uncomfortable with totals that are merely &lt;em&gt;"plausible"&lt;/em&gt;. If a balance sheet doesn't balance, or if a portfolio valuation cannot be traced back to the underlying statement byte, it is wrong.&lt;/p&gt;

&lt;p&gt;About a year ago, I reached my breaking point with consumer fintech. &lt;/p&gt;




&lt;h2&gt;
  
  
  🇮🇳 The Problem: The Chaos of Indian Personal Finance
&lt;/h2&gt;

&lt;p&gt;If you manage personal finances in India, your data is scattered across incompatible walled gardens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mutual Funds&lt;/strong&gt;: CAMS and KFintech CAS (Consolidated Account Statements) in password-protected PDFs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domestic Equities&lt;/strong&gt;: Zerodha, Groww, or Angel One trade books and contract notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retirement&lt;/strong&gt;: EPFO passbooks with annual interest crediting delays, plus PPF and NPS statements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foreign Equities&lt;/strong&gt;: US stocks held via INDmoney or Vested, requiring complex RBI LRS tracking, TCS deductions, and Schedule FA disclosures for Indian tax returns (ITR-2 / ITR-3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taxation&lt;/strong&gt;: Computing Section 112A Long Term Capital Gains (LTCG) with grandfathering rules, the ₹1.25 Lakh exemption limit, and strict FIFO (First-In, First-Out) lot depletion across bonus shares and stock splits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most people try to solve this by uploading their most sensitive financial documents to venture-backed fintech apps. But these apps frequently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Harvest your financial data to cross-sell personal loans and credit cards.&lt;/li&gt;
&lt;li&gt;Get acquired or shut down without warning (the fate of Mint and countless others).&lt;/li&gt;
&lt;li&gt;Oversimplify the math, giving you pretty pie charts while butchering actual realized capital gains or currency attribution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of accepting that, I spent months doing what any data-obsessed CA would do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I built an open-source, local-first medallion lakehouse and quantitative decision engine for an audience of exactly one person (myself).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s called &lt;strong&gt;Personal Finance ETL&lt;/strong&gt;, and it runs 100% locally on my machine silicon with zero telemetry and zero cloud bills.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Real Production Metrics: Not a Five-Row Demo
&lt;/h2&gt;

&lt;p&gt;Open source projects often demo toy datasets with 10 rows. Here is the actual scale of my personal financial production baseline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Production Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raw Source Artifacts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1,600+&lt;/strong&gt; statements, trade books, and passbooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Investment Instruments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;147&lt;/strong&gt; active &amp;amp; historical workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Published Rows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;449,000+&lt;/strong&gt; Silver &amp;amp; Gold warehouse records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Registered Contracts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;66&lt;/strong&gt; explicit contracts (20 Bronze, 24 Silver, 22 Gold)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fresh Ingestion + Rebuild&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;40–45 seconds&lt;/strong&gt; (entire 4-year history from scratch)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Warm-Cache Pipeline Run&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23.95 seconds&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analytical Query Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;5 to 15 milliseconds&lt;/strong&gt; on DuckDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monte Carlo Simulation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;38 milliseconds&lt;/strong&gt; for 10,000 trajectories via Numba&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🏗 The Architecture: Why Two Separate Databases?
&lt;/h2&gt;

&lt;p&gt;When building local-first, you don't have AWS credits or Snowflake auto-scaling to mask architectural flaws. You are sharing resources with your operating system, browser, and IDE.&lt;/p&gt;

&lt;p&gt;Early on, I realized that &lt;strong&gt;transactional state&lt;/strong&gt; and &lt;strong&gt;analytical querying&lt;/strong&gt; need two fundamentally different engines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                      Your Local Machine                     │
│                                                             │
│  CAS PDFs, Zerodha CSVs, EPF Passbooks, INDmoney Statements │
│         │                                                   │
│         ▼ (SHA-256 Content-Addressable Dedup)               │
│  ┌───────────────────────────────┐                          │
│  │   SQLite Control Plane        │ ── ACID Run Manifests    │
│  │   (WAL Mode, zero analytics)  │ ── Pipeline State Logs   │
│  └──────────────┬────────────────┘ ── Hot Backup Snapshots  │
│                 │                                           │
│                 ▼                                           │
│  ┌───────────────────────────────┐                          │
│  │      Polars Engine            │ ── Vectorized FIFO Match │
│  │   (LazyFrames, Zero-Copy)     │ ── FX RBI Attribution    │
│  └──────────────┬────────────────┘                          │
│                 │ (Arrow IPC Stream)                        │
│                 ▼                                           │
│  ┌───────────────────────────────┐                          │
│  │    DuckDB OLAP Warehouse      │                          │
│  │  ├─ Bronze: Raw Ingestion     │ ── Analytical Lakehouse  │
│  │  ├─ Silver: Canonical Facts   │ ── 66 Strict Contracts   │
│  │  └─ Gold:   Decision Marts    │ ── Sub-15ms Reporting   │
│  └───────────────────────────────┘                          │
│                 │                                           │
│                 ▼                                           │
│  ┌───────────────────────────────┐                          │
│  │  Numba Stochastic Simulation  │ ── 10k Paths in 38ms     │
│  └───────────────────────────────┘                          │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. SQLite: The Transactional Control Plane
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role&lt;/strong&gt;: Operational watchdog.&lt;/li&gt;
&lt;li&gt;Runs in &lt;strong&gt;WAL (Write-Ahead Logging)&lt;/strong&gt; mode.&lt;/li&gt;
&lt;li&gt;Stores &lt;strong&gt;zero&lt;/strong&gt; financial balances or analytics.&lt;/li&gt;
&lt;li&gt;Manages raw statement SHA-256 hashes, pipeline run manifests, and audit state.&lt;/li&gt;
&lt;li&gt;If an ingestion process crashes or a bank re-exports the same statement with a different file name, SQLite intercepts it cleanly without polluting the warehouse.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. DuckDB: The Columnar Analytical Warehouse
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role&lt;/strong&gt;: OLAP analytics and dimensional reporting.&lt;/li&gt;
&lt;li&gt;Organizes financial data into a classical &lt;strong&gt;Medallion Architecture&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bronze&lt;/strong&gt;: Raw tabular mirrors of bank exports, contract notes, and EPF ledgers. Totally immutable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silver&lt;/strong&gt;: Canonical financial facts. Ingestion-specific quirks are stripped. Multi-currency trades are matched with historical daily exchange rates, and investment lots are tracked via FIFO depletion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gold&lt;/strong&gt;: Ready-to-query dimensional marts for net worth timelines, tax realization schedules, and benchmark shadow portfolios (comparing real returns against Nifty 50 or S&amp;amp;P 500 shadow indices).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛡 Grounded in Real Code: The 66 Data Contracts
&lt;/h2&gt;

&lt;p&gt;In our warehouse, contracts aren't vague concepts—they are strongly typed Python models and strict DuckDB DDL schemas.&lt;/p&gt;

&lt;p&gt;Here is the exact in-memory &lt;code&gt;TaxLot&lt;/code&gt; model that represents active acquisition tranches (&lt;code&gt;domain/investments/contracts.py&lt;/code&gt;):&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="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slots&lt;/span&gt;&lt;span class="o"&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;class&lt;/span&gt; &lt;span class="nc"&gt;TaxLot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Represents a single active purchase lot in memory.&lt;/span&gt;&lt;span class="sh"&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;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&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;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;bm_buy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&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;shadow_qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;price_local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="n"&gt;fx_rate_buy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="n"&gt;currency_id&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="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;bm_buy_local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&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;purchase_id&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="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;lot_id&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="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;lot_source_type&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="s"&gt;PURCHASE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the corresponding DuckDB Silver contract for realized sale events (&lt;code&gt;infrastructure/warehouse/schema/silver.py&lt;/code&gt;), capturing separate asset PnL, foreign exchange PnL, and tax rates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;silver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f_Investment_Realized_Events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Realized_Event_ID&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sale_ID&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Lot_ID&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Purchase_ID&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ISIN&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Acquisition_Date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Disposal_Date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FY&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Quantity_Disposed&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Acquisition_Price&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Disposed_Cost_Basis&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sale_Price&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sale_Proceeds&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Realized_Gain_Loss&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Holding_Type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;-- LTCG vs STCG&lt;/span&gt;
    &lt;span class="n"&gt;Tax_Type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Tax_Subtype&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Lot_Source_Type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Currency_ID&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Asset_PnL_Local&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- Gain in local currency (e.g. USD)&lt;/span&gt;
    &lt;span class="n"&gt;Asset_PnL&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- Converted asset gain in INR&lt;/span&gt;
    &lt;span class="n"&gt;Forex_PnL&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- Currency fluctuation gain in INR&lt;/span&gt;
    &lt;span class="n"&gt;Applied_Rate&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- Tax rate resolved from macro parameters&lt;/span&gt;
    &lt;span class="n"&gt;Estimated_Tax&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sale_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Lot_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚡ Real FIFO Math: Separating Asset PnL from Forex PnL
&lt;/h2&gt;

&lt;p&gt;When selling foreign equities (e.g. US stocks held via INDmoney), computing capital gains is notoriously tricky. If the stock went up 20% in USD, but the USD/INR exchange rate went from ₹75 to ₹84, what part of your gain is stock return versus currency appreciation?&lt;/p&gt;

&lt;p&gt;Here is the exact math running in our FIFO depletion engine (&lt;code&gt;domain/investments/lots/fifo.py&lt;/code&gt;):&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="c1"&gt;# From FIFOPortfolio.sell() in domain/investments/lots/fifo.py
&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&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;_active_lots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;lot&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="n"&gt;_active_lots&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;consumed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qty&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;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&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;pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="n"&gt;asset_pnl_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="n"&gt;asset_pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="n"&gt;forex_pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;consumed&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency_id&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="n"&gt;default_currency_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Asset PnL is the pure equity price change in USD, converted at disposal FX
&lt;/span&gt;            &lt;span class="n"&gt;asset_pnl_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_local&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price_local&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;consumed&lt;/span&gt;
            &lt;span class="n"&gt;asset_pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asset_pnl_local&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fx_rate_sell&lt;/span&gt;
            &lt;span class="c1"&gt;# Forex PnL is the currency movement on the original acquisition cost
&lt;/span&gt;            &lt;span class="n"&gt;forex_pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price_local&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fx_rate_sell&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fx_rate_buy&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;consumed&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;asset_pnl_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pnl&lt;/span&gt;
            &lt;span class="n"&gt;asset_pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pnl&lt;/span&gt;
            &lt;span class="n"&gt;forex_pnl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;

    &lt;span class="n"&gt;realized_event_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_deterministic_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REALIZED&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;Sale_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;sale_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lot_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;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Consumed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;consumed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every penny of gain is deterministically decomposed into asset performance versus currency movement, completely eliminating tax ambiguity.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Accelerating Monte Carlo Simulations: 14.2s ➔ 38ms with Numba
&lt;/h2&gt;

&lt;p&gt;In India, retirement (FIRE) planning cannot rely on generic US rules like the "4% Trinity rule". Real inflation in India typically runs at 6–7%, and sequence-of-returns risk during a market crash can devastate a corpus if it hits in the first few years of retirement.&lt;/p&gt;

&lt;p&gt;To model this, the simulation engine implements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;3-regime Markov chain&lt;/strong&gt; (Bull, Bear, Stagnation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human capital shocks&lt;/strong&gt; (unemployment / income interruption probabilities)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guyton-Klinger dynamic withdrawal guardrails&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ornstein-Uhlenbeck mean-reverting inflation dynamics&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the actual Numba-compiled simulation kernel (&lt;code&gt;domain/fire/simulation.py&lt;/code&gt;):&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="nd"&gt;@njit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fastmath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&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;def&lt;/span&gt; &lt;span class="nf"&gt;_run_mc_simulations_numba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pv_arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;pmt_total_arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;fv_total_arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;burn_total_arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;inf_rates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;seed_ints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;gens&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="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vol_r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;mean_r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;swr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;current_age_months_arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;target_lifespan_months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tm_flat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;state_bull_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;state_bear_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;state_stag_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;hc_shock_prob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;hc_shock_min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;hc_shock_max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="bp"&gt;...&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;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;npt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NDArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...]:&lt;/span&gt;
    &lt;span class="n"&gt;n_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pv_arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Evaluates 10,000 trajectories in parallel across CPU cores
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;prange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_rows&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Markov regime updates + stochastic returns + adaptive withdrawals
&lt;/span&gt;        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By compiling down to native machine code with LLVM and utilizing &lt;code&gt;parallel=True&lt;/code&gt; via OpenMP/TBB, evaluating &lt;strong&gt;10,000 trajectories over 40 years (4.8 million state evaluations)&lt;/strong&gt; dropped from &lt;strong&gt;14.2 seconds down to 38 milliseconds&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 A Pragmatic View on AI &amp;amp; Engineering Rigor
&lt;/h2&gt;

&lt;p&gt;In 2026, building a large software project as a solo developer almost certainly involves AI. I used Claude and Gemini throughout development—primarily as a fast pair-programmer for scaffolding repetitive test fixtures, drafting docstrings, and deciphering obscure mypy/ruff errors.&lt;/p&gt;

&lt;p&gt;However, financial software has a unique, unforgiving property: &lt;strong&gt;it is binary&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Either a balance sheet reconciles to the exact rupee, or it is wrong. Either a foreign capital gains lot matches its historical RBI exchange rate, or the tax filing is incorrect. A 95% statistically plausible answer from an LLM is a 100% defective accounting system.&lt;/p&gt;

&lt;p&gt;That's why I kept AI strictly at the perimeter (documentation, test scaffolding, and IDE diagnostics). The core system is governed entirely by deterministic software engineering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;66 Explicit Data Contracts&lt;/strong&gt;: Ingestion stops cold if schemas or types drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Loose Types&lt;/strong&gt;: The entire codebase passes &lt;code&gt;mypy --strict&lt;/code&gt; and &lt;code&gt;pyright&lt;/code&gt; in strict mode with zero type ignores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable Lineage&lt;/strong&gt;: Every single number displayed in a Gold mart can be traced back through Silver facts to the exact SHA-256 hash of the original bank statement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My working rule for financial architecture:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A conversational AI interface over an unverified data model doesn't fix ambiguity—it just hallucinates with confidence.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Trust comes from deterministic data contracts, explicit reconciliation controls, and reproducible code.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 How to Try It
&lt;/h2&gt;

&lt;p&gt;The project is packaged with &lt;code&gt;uv&lt;/code&gt; and available on PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install via pip&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;personal-finance-etl

&lt;span class="c"&gt;# Launch the desktop UI&lt;/span&gt;
personal-finance-etl-gui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or clone the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tks18/personal-finance-etl.git
&lt;span class="nb"&gt;cd &lt;/span&gt;personal-finance-etl
uv &lt;span class="nb"&gt;sync
&lt;/span&gt;uv run poe gui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Links &amp;amp; Docs:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/tks18/personal-finance-etl" rel="noopener noreferrer"&gt;tks18/personal-finance-etl&lt;/a&gt; (GPLv3)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wiki &amp;amp; Deep Architecture Tour&lt;/strong&gt;: &lt;a href="https://github.com/tks18/personal-finance-etl/wiki" rel="noopener noreferrer"&gt;GitHub Wiki Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PyPI&lt;/strong&gt;: &lt;a href="https://pypi.org/project/personal-finance-etl/" rel="noopener noreferrer"&gt;pypi.org/project/personal-finance-etl&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Building an enterprise-grade lakehouse with 66 data contracts, dual databases, and compiled Numba kernels just to track my own investments and expenses might be textbook overengineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But it works.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I have complete data sovereignty, mathematically verifiable tax numbers, sub-15ms SQL queries, and zero SaaS subscription anxiety.&lt;/p&gt;

&lt;p&gt;I'd love to hear from other developers and finance folks in the comments: &lt;strong&gt;How do you manage your personal financial data? Have you built your own local tooling, or are you still relying on spreadsheets?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>data</category>
      <category>database</category>
      <category>python</category>
    </item>
  </channel>
</rss>
