<?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: shakti tiwari </title>
    <description>The latest articles on DEV Community by shakti tiwari  (@shaktitiwari).</description>
    <link>https://dev.to/shaktitiwari</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%2F4036359%2F405db913-daa8-4c68-91cf-97dd59d8c5bf.jpg</url>
      <title>DEV Community: shakti tiwari </title>
      <link>https://dev.to/shaktitiwari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shaktitiwari"/>
    <language>en</language>
    <item>
      <title>Data Contracts for Quant Pipelines: Schema Enforcement at Ingest</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Sun, 23 Aug 2026 03:33:35 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/data-contracts-for-quant-pipelines-schema-enforcement-at-ingest-90g</link>
      <guid>https://dev.to/shaktitiwari/data-contracts-for-quant-pipelines-schema-enforcement-at-ingest-90g</guid>
      <description>&lt;h1&gt;
  
  
  Data Contracts for Quant Pipelines: Schema Enforcement at Ingest
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Shakti Tiwari · Engineering note · Educational only · Not investment advice&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A quant pipeline is a chain of trust. Raw bytes arrive from an exchange feed, a vendor API, an internal log, or a partner export. They travel through parsing, normalization, feature construction, labeling, and finally into a model or a backtest. The weakest link in that chain is almost never the clever part — the gradient, the regime detector, the volatility surface fit. The weakest link is the very first step: ingest. If the data that enters the pipeline is silently wrong, every downstream computation inherits that wrongness and compounds it. A model trained on corrupted inputs does not fail loudly; it fails plausibly. That is the most dangerous failure mode in quantitative work, and it is the one a data contract is designed to prevent.&lt;/p&gt;

&lt;p&gt;This article is about data contracts for quant pipelines, and specifically about enforcing them at the point of ingest rather than deep inside the pipeline. We will build the concept from first principles, look at the failure modes that contracts exist to stop, write real code for a schema-enforcing ingest gate, and derive the simple statistics that make enforcement decisions objective instead of vibes. There are no live market numbers here; everything is structural and reproducible against your own data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a data contract actually is
&lt;/h2&gt;

&lt;p&gt;A data contract is a formal, machine-checkable agreement between a producer of data and a consumer of data. It states, in a form a program can verify, what the data promises to look like: which columns exist, what types they carry, which are nullable, which ranges are legal, what units apply, how fresh the data must be, and what volume to expect. In a quant context the producer might be a market-data collector or a vendor, and the consumer is the feature store, the backtest engine, or the live inference path.&lt;/p&gt;

&lt;p&gt;The contract is not documentation. Documentation is a paragraph someone wrote and nobody re-reads. A contract is code that runs on every record, every batch, every file, before that data is allowed to touch anything downstream. If the data violates the contract, it does not get a free pass because the dashboard looks okay. It is rejected, quarantined, or flagged, by rule.&lt;/p&gt;

&lt;p&gt;There are four layers that a mature contract stack usually separates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structural / schema contract&lt;/strong&gt; — column names, types, nullability, primary keys. This is the layer most people mean when they say "schema enforcement," and it is the focus of this article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic contract&lt;/strong&gt; — the meaning of a field. A column called &lt;code&gt;price&lt;/code&gt; must be a positive number in the instrument's trading currency; a column called &lt;code&gt;ts&lt;/code&gt; must be epoch milliseconds, not seconds, not a date string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SLA contract&lt;/strong&gt; — freshness and volume. The batch must arrive within a window; the row count must fall inside an expected band.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribution contract&lt;/strong&gt; — the statistical shape. The null rate of &lt;code&gt;price&lt;/code&gt; must stay below a threshold; the distribution of &lt;code&gt;return&lt;/code&gt; must not suddenly shift. This is where drift detection lives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Schema enforcement at ingest means you check layers one and two (and ideally three) the instant data crosses the boundary into your system, before it is persisted, joined, or modeled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ingest is the right place, not the middle
&lt;/h2&gt;

&lt;p&gt;It is tempting to validate data "somewhere downstream," after it has been transformed a few times, when it is convenient to write a test. That temptation is a trap. Three reasons make ingest the only correct enforcement point.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;cost of correction grows downstream&lt;/strong&gt;. A single mistyped column that slips through ingest can propagate into a feature table, then into a label, then into a trained model artifact, then into a published backtest. Finding the root cause after the fact means recomputing every stage. Catching it at ingest means dropping or fixing one batch.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;trust is binary at the boundary&lt;/strong&gt;. Once data is in your warehouse, other engineers and other jobs will assume it is "your clean data." If you let a malformed batch in, you have implicitly signed off on it. The boundary is where the signature belongs.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;reproducibility depends on it&lt;/strong&gt;. A backtest is only reproducible if the input it consumed is exactly the input you think it consumed. A schema gate that records what was accepted (and what was rejected) gives you an auditable ledger of inputs. Without that ledger, "I reran the backtest" is not reproducible; it is a hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure modes contracts exist to stop
&lt;/h2&gt;

&lt;p&gt;Un-contracted ingest fails in predictable ways. Naming them makes the contract design obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type drift.&lt;/strong&gt; A vendor silently changes a field from integer to string, or from float to string-encoded number. Your parser does not crash; it coerces, or it stores the string, and three weeks later a feature computed as &lt;code&gt;float(price)&lt;/code&gt; throws at two in the morning during a live run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit drift.&lt;/strong&gt; A feed switches a price from paise to rupees, or a timestamp from seconds to milliseconds. Nothing errors. Your features are just wrong by a factor of one hundred or one thousand. This is the silent killer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing columns.&lt;/strong&gt; A schema change removes a column your labels depend on. If you tolerate missing columns, your label computation produces &lt;code&gt;NaN&lt;/code&gt; rows that you may or may not notice before they poison training.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Null injection.&lt;/strong&gt; A new release starts emitting nulls for a field that was previously always populated. Your model treats null as a real value via imputation and drifts quietly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume anomalies.&lt;/strong&gt; A partial file arrives — only a slice of the symbols, or only the first hour of the session. If you ingest it without a volume check, your features describe a half-day and your backtest describes a market that never existed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicate keys.&lt;/strong&gt; Reconnects and retries resend rows. Without a primary-key contract, you double-count, and double-counting inflates sample sizes and corrupts statistics. (This dovetails with the idempotent ingest work covered in the linked pipeline article.)&lt;/p&gt;

&lt;p&gt;Every one of these is cheap to detect at ingest and expensive to discover later.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal contract, expressed as code
&lt;/h2&gt;

&lt;p&gt;The cleanest way to make a contract real is to express it as a typed schema object and validate incoming records against it before persistence. Below is a self-contained validator written in plain Python with no heavy dependencies, so you can drop it into any pipeline. It shows the shape of the idea; in production you would back it with a library such as a Pandas schema validator or a JSON Schema validator, but the logic is identical.&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;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContractViolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Raised when a record or batch breaks the data contract.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FieldSpec&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="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;                 &lt;span class="c1"&gt;# int, float, str, bool
&lt;/span&gt;    &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# semantic guard, e.g. "ms", "INR"
&lt;/span&gt;    &lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# format guard for strings
&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SchemaContract&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="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="n"&gt;FieldSpec&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&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;primary_key&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&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;min_rows&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;1&lt;/span&gt;
    &lt;span class="n"&gt;max_rows&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;10_000_000&lt;/span&gt;   &lt;span class="c1"&gt;# guard against absurd batches
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_record&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;rec&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="bp"&gt;None&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;spec&lt;/span&gt; &lt;span class="ow"&gt;in&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;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&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="bp"&gt;None&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;value&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nullable&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;ContractViolation&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;non-nullable field &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; is missing/Null&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="c1"&gt;# strict type check; coercion is a separate explicit step
&lt;/span&gt;                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ContractViolation&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;field &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&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;__name__&lt;/span&gt;&lt;span class="si"&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;if&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_value&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;ContractViolation&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;field &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; below min &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="si"&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;if&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_value&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;ContractViolation&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;field &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; above max &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="si"&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;if&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ContractViolation&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;field &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; fails format &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="si"&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;def&lt;/span&gt; &lt;span class="nf"&gt;validate_batch&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;rows&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;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="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;n&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;rows&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;min_rows&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;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;max_rows&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;ContractViolation&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;row count &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; outside [&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;min_rows&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&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;max_rows&lt;/span&gt;&lt;span class="si"&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;seen_keys&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="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&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="nf"&gt;validate_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&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;primary_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;primary_key&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;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen_keys&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;ContractViolation&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;duplicate primary key &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at row &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&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;seen_keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This contract does four concrete things per record: it enforces non-nullability, it enforces the exact Python type (no silent coercion), it enforces value ranges, and it enforces a string format. Per batch it enforces a row-count band and primary-key uniqueness. That single object, run before persistence, stops type drift, unit drift (via the &lt;code&gt;unit&lt;/code&gt; annotation used in a fuller implementation), null injection, volume anomalies, and duplicate keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coercion versus rejection: a deliberate choice
&lt;/h2&gt;

&lt;p&gt;A tempting "helpful" behavior is to coerce. See a string &lt;code&gt;"123.45"&lt;/code&gt; where a float is expected? Coerce it. See an integer &lt;code&gt;100&lt;/code&gt; where a float is expected? Coerce it. The problem is that coercion hides the contract breach. If the producer is sending strings where they promised floats, that is a bug on their side, and you want it to be loud, not laundered into a "successful" ingest.&lt;/p&gt;

&lt;p&gt;The disciplined pattern is: &lt;strong&gt;reject or quarantine on type mismatch, coerce only along explicit, declared widening paths.&lt;/strong&gt; Widening from &lt;code&gt;int&lt;/code&gt; to &lt;code&gt;float&lt;/code&gt; is safe and reversible in the sense that no information is lost; coercing &lt;code&gt;str&lt;/code&gt; to &lt;code&gt;float&lt;/code&gt; is not safe and must be treated as a violation. The validator above takes the strict stance. In a real system you would add an explicit &lt;code&gt;coerce&lt;/code&gt; set to the contract, e.g. &lt;code&gt;{("int", "float")}&lt;/code&gt;, and reject everything else. The point is that the choice is made once, in the contract, not ad hoc in a dozen parsing functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Range and null-rate thresholds as formulas
&lt;/h2&gt;

&lt;p&gt;Two numeric guards turn "looks fine" into "objectively fine": a per-field range check and a per-field null-rate check. Both have simple formulas you can compute in a single pass.&lt;/p&gt;

&lt;p&gt;For a field &lt;code&gt;x&lt;/code&gt; over a batch of &lt;code&gt;N&lt;/code&gt; records, the &lt;strong&gt;observed null rate&lt;/strong&gt; is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;null_rate(x) = (count of records where x is None) / N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The contract accepts the batch only if &lt;code&gt;null_rate(x) &amp;lt;= T_x&lt;/code&gt;, where &lt;code&gt;T_x&lt;/code&gt; is the maximum tolerable null fraction for that field. For a field that the label depends on, &lt;code&gt;T_x&lt;/code&gt; is typically zero; for an auxiliary field it might be a small value such as a few percent. Notice that &lt;code&gt;T_x&lt;/code&gt; is a contract parameter, not a guess — it is decided when the contract is written, and changing it is a reviewed change, not a silent drift.&lt;/p&gt;

&lt;p&gt;For a numeric field, the &lt;strong&gt;observed range&lt;/strong&gt; is the pair &lt;code&gt;(min_x, max_x)&lt;/code&gt; across the batch:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accept iff  L_x &amp;lt;= min_x  and  max_x &amp;lt;= U_x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;where &lt;code&gt;(L_x, U_x)&lt;/code&gt; is the contract's declared legal interval. This catches unit drift immediately: if &lt;code&gt;price&lt;/code&gt; is supposed to be in rupees and a batch arrives in paise, &lt;code&gt;max_x&lt;/code&gt; jumps by a factor of one hundred and the batch is rejected before it can corrupt a feature.&lt;/p&gt;

&lt;p&gt;A useful companion metric is the &lt;strong&gt;freshness&lt;/strong&gt; of a batch, measured as the gap between ingest time and the newest record timestamp:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;freshness = ingest_ts - max(record_ts)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The SLA contract declares &lt;code&gt;freshness &amp;lt;= F&lt;/code&gt;. A batch that is too old is quarantined: it may be perfectly typed and yet stale, and stale data in a live model is its own kind of corruption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail-fast versus quarantine
&lt;/h2&gt;

&lt;p&gt;Once a breach is detected, you have two sane responses and one insane one.&lt;/p&gt;

&lt;p&gt;The insane response is &lt;strong&gt;ingest-and-warn&lt;/strong&gt;: write the bad data anyway and emit a log line nobody reads. This is how silent corruption enters production.&lt;/p&gt;

&lt;p&gt;The two sane responses are &lt;strong&gt;fail-fast&lt;/strong&gt; and &lt;strong&gt;quarantine&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fail-fast&lt;/strong&gt; rejects the entire batch and halts the pipeline stage. Use this when a breach means the data is unusable and there is a human or an upstream owner who will fix and resend. Backtests and offline feature builds usually want fail-fast: a partial or malformed input should not produce a "result."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quarantine&lt;/strong&gt; writes the offending records (or batch) to a side location and lets the clean portion proceed. Use this in live ingest where dropping a whole session because of one bad symbol is worse than routing the bad symbol aside. Quarantine must still be loud: it feeds the same alerting as fail-fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A practical implementation tags each rejected batch with the violation type and the count, so monitoring can tell you whether breaches are sporadic (a flaky vendor) or systematic (a real schema change you must adapt the contract to).&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema versioning and compatibility
&lt;/h2&gt;

&lt;p&gt;Producers change. That is not a bug; it is life. The contract must version so that a legitimate, reviewed change does not look identical to a silent breach. The standard discipline borrows from API versioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backward compatibility&lt;/strong&gt;: a new contract version can add optional fields and relax constraints, but must not remove or narrow fields that old consumers depend on. Old consumers keep working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward compatibility&lt;/strong&gt;: a consumer written against a newer contract can tolerate older data by ignoring unknown optional fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At ingest you store the contract version alongside the data. If incoming data declares &lt;code&gt;contract_version = 3&lt;/code&gt; but your pipeline only knows &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt;, that is a hard stop: someone shipped a change nobody told the consumer about. If data declares &lt;code&gt;2&lt;/code&gt; and you know &lt;code&gt;3&lt;/code&gt;, you accept with a deprecation note. The key is that version is a first-class field, checked exactly like any other.&lt;/p&gt;

&lt;p&gt;A clean way to encode compatibility is a small compatibility matrix evaluated at startup:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compatible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumer_version&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;producer_version&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# backward compatible: consumer accepts &amp;lt;= its own version
&lt;/span&gt;    &lt;span class="c1"&gt;# forward tolerant: consumer accepts one minor ahead via optional-field handling
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;producer_version&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;consumer_version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real deployment you would separate major and minor versions and apply the stricter major-compatibility rule, but the principle holds: version is checked, not assumed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distribution contracts: when structure is not enough
&lt;/h2&gt;

&lt;p&gt;Schema checks confirm the data is &lt;em&gt;shaped&lt;/em&gt; correctly. They do not confirm it is &lt;em&gt;behaving&lt;/em&gt; correctly. A column can be perfectly typed, non-null, in-range, and still be statistically wrong — for example, a &lt;code&gt;return&lt;/code&gt; field that used to be centered near zero now sits at a large constant because of a bug upstream. This is where a distribution contract earns its place.&lt;/p&gt;

&lt;p&gt;The workhorse metric is the &lt;strong&gt;Population Stability Index (PSI)&lt;/strong&gt;, which quantifies how much a feature's distribution has shifted between a reference period and the current batch:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PSI = sum over bins i of  (actual_i - expected_i) * ln(actual_i / expected_i)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;where &lt;code&gt;actual_i&lt;/code&gt; and &lt;code&gt;expected_i&lt;/code&gt; are the fraction of records falling in bin &lt;code&gt;i&lt;/code&gt; for the current and reference distributions respectively. A PSI below a small threshold (commonly a low single-digit percentage in practice, but the exact threshold is a contract parameter you set) means the distribution is stable; a larger PSI means it has moved enough to warrant investigation before the data feeds a model.&lt;/p&gt;

&lt;p&gt;For a two-sample test on a continuous field you can also use the &lt;strong&gt;Kolmogorov–Smirnov statistic&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D = sup_x | F_actual(x) - F_expected(x) |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;the maximum vertical distance between the two empirical cumulative distribution functions. A large &lt;code&gt;D&lt;/code&gt; rejects the hypothesis that the two samples come from the same distribution. Either metric turns "the numbers feel off" into a numeric, contract-enforced gate.&lt;/p&gt;

&lt;p&gt;These distribution checks sit one layer above schema enforcement. You should still run schema enforcement first — there is no point computing PSI on a column that failed the type check.&lt;/p&gt;

&lt;h2&gt;
  
  
  A schema registry pattern
&lt;/h2&gt;

&lt;p&gt;As you accumulate contracts, you do not want them scattered across services as hardcoded dictionaries. The registry pattern centralizes them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contracts live in version-controlled files (YAML or JSON) in a dedicated repository.&lt;/li&gt;
&lt;li&gt;An ingest service fetches the contract for a given &lt;code&gt;dataset_id&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; from the registry at startup.&lt;/li&gt;
&lt;li&gt;Validation uses the fetched contract, so changing a threshold is a reviewed pull request, not a silent edit on a production box.&lt;/li&gt;
&lt;li&gt;The registry also stores the reference distributions used by distribution contracts, so PSI baselines are themselves versioned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Centralization has a second benefit: you can run the &lt;em&gt;same&lt;/em&gt; contract in three places — the producer's pre-publish test, the ingest gate, and a nightly audit — and they cannot drift apart because they read the same file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into CI and monitoring
&lt;/h2&gt;

&lt;p&gt;A contract that only runs in production is half a contract. The producer should validate against the contract in continuous integration before shipping a data change, using a sample of real records. If the sample would breach the contract, the change is blocked. This moves the failure from "three a.m. page" to "merge-time error," which is where you want it.&lt;/p&gt;

&lt;p&gt;On the consumer side, every ingest run should emit metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;batches accepted and rejected, by violation type;&lt;/li&gt;
&lt;li&gt;null rates per critical field, compared to &lt;code&gt;T_x&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;row counts compared to the expected band;&lt;/li&gt;
&lt;li&gt;freshness compared to &lt;code&gt;F&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These feed dashboards and alerts. A sudden spike in &lt;code&gt;type_mismatch&lt;/code&gt; violations is the earliest signal of a vendor change; a creeping &lt;code&gt;null_rate&lt;/code&gt; on a label field is the earliest signal of a slow upstream regression. Both are caught days before they would surface as a mysteriously degraded model.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this connects to the rest of the pipeline
&lt;/h2&gt;

&lt;p&gt;Schema enforcement at ingest is not a standalone ritual; it is the foundation the rest of the quant stack stands on. The idempotent collector article explains how to stop duplicate ticks at the socket layer — the primary-key clause in the contract above is the persistence-layer echo of that same idea, defense in depth. The broader data-pipeline article shows how clean ticks become a feature store; a feature store is only trustworthy if the ticks entering it passed a contract, because the feature code assumes the columns and types it was written against.&lt;/p&gt;

&lt;p&gt;For backtesting the link is even sharper. A backtest is a claim about how a strategy would have performed. That claim is only as good as the inputs. If a malformed batch slipped past ingest and biased a feature, the backtest reports a result that never could have happened — and you would not know, because the numbers would look reasonable. The contract is what makes "I trust this backtest" a defensible statement rather than a hope. This is the same epistemic discipline that governs every article on this site: verify the input before you trust the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes to avoid
&lt;/h2&gt;

&lt;p&gt;The errors teams make around data contracts are consistent, which means they are avoidable if named.&lt;/p&gt;

&lt;p&gt;The first mistake is &lt;strong&gt;treating the contract as documentation&lt;/strong&gt;. A paragraph in a wiki is not a contract. If a program does not evaluate it on every record, it is decoration.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;coercing instead of rejecting&lt;/strong&gt;. Coercion converts a loud, fixable breach into a silent, compounding one. Widen explicitly or reject; never launder.&lt;/p&gt;

&lt;p&gt;The third is &lt;strong&gt;versioning by hope&lt;/strong&gt; — changing a schema in place and assuming downstream consumers will cope. Version the contract, check the version at ingest, and make incompatibility a hard stop.&lt;/p&gt;

&lt;p&gt;The fourth is &lt;strong&gt;forgetting the distribution layer&lt;/strong&gt;. Type-correct data can still be statistically broken. Schema is necessary, not sufficient; add PSI or KS gates for critical fields.&lt;/p&gt;

&lt;p&gt;The fifth is &lt;strong&gt;no metric emit&lt;/strong&gt;. A contract that fails silently in a log file nobody reads has failed in spirit. Rejection counts and null rates must be visible.&lt;/p&gt;

&lt;p&gt;Avoid these five and you have already out-engineered most pipelines you will encounter, not because the math is exotic but because the discipline is consistent.&lt;/p&gt;

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

&lt;p&gt;Data contracts for quant pipelines are the cheapest insurance in the entire stack. The idea is simple: state, in code, what your data promises to be, and check that promise at the boundary where data enters your world — before it is persisted, joined, modeled, or backtested. Schema enforcement handles shape and type; semantic and SLA clauses handle units, freshness, and volume; distribution clauses handle statistical behavior through PSI and KS. Together they convert "the data looks okay" into "the data passed an auditable, versioned, numeric gate."&lt;/p&gt;

&lt;p&gt;The payoff is not a single prevented outage. The payoff is that every downstream result — every feature, every label, every backtest, every live inference — rests on inputs you can name and defend. In quantitative work, where a silent wrong number can masquerade as an edge, that defensibility is the whole game. Write the contract once, enforce it everywhere, version it deliberately, and let the metrics tell you when the world changed. Everything else in the pipeline is easier when the floor is solid.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Shakti Tiwari writes about AI, local AI agents, XGBoost, and systematic quantitative trading for Indian markets — code-first, no-hype, governed by an epistemic firewall against fabricated numbers. Educational, reproducible, and source-attributed. Follow on X, LinkedIn, GitHub, and DEV via the links below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐦 X: &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;https://x.com/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 LinkedIn: &lt;a href="https://linkedin.com/in/shakti-tiwari-a3b22a38b" rel="noopener noreferrer"&gt;https://linkedin.com/in/shakti-tiwari-a3b22a38b&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/shaktitiwari715-ai" rel="noopener noreferrer"&gt;https://github.com/shaktitiwari715-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📝 DEV.to: &lt;a href="https://dev.to/shaktitiwari"&gt;https://dev.to/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 Site: &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;https://optiontradingwithai.in&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Educational only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue Reading (Authority OS series)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/nifty-options-data-pipeline-from-websocket-tick-to-clean-feature-store-30e2"&gt;Nifty Options Data Pipeline: From WebSocket Tick to Clean Feature Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/how-to-make-a-websocket-market-data-collector-idempotent-after-reconnects-2dpm"&gt;Idempotent WebSocket Market Data Collector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/nifty-options-xgboost-a-leakage-free-ml-pipeline-no-overfitting-no-promises-4nf6"&gt;Leakage-Free XGBoost Pipeline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/ai-trading-myth-buster-5-things-ml-will-not-do-for-your-nifty-options-15di"&gt;AI Trading Myth-Buster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/interactive-tools-for-nifty-options-traders-free-no-signup-4j8m"&gt;Interactive Tools Hub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;h1&gt;
  
  
  ShaktiTiwariOnAI #QuantFinance #DataContracts #DataEngineering #Python #MachineLearning #TradingAIBharat #Backtesting
&lt;/h1&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>dataengineering</category>
      <category>quant</category>
    </item>
    <item>
      <title>Tabular Data for Nifty Traders — Why XGBoost Beats Deep Learning (with Real 120-Day Data)</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Sat, 22 Aug 2026 17:01:34 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/tabular-data-for-nifty-traders-why-xgboost-beats-deep-learning-with-real-120-day-data-2j67</link>
      <guid>https://dev.to/shaktitiwari/tabular-data-for-nifty-traders-why-xgboost-beats-deep-learning-with-real-120-day-data-2j67</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; Content is educational only and is not financial, investment, or trading advice. Shakti Tiwari is NISM-Series-XII certified; not a SEBI-registered Research Analyst. Verify everything with a SEBI-registered IA before acting.&lt;/p&gt;

&lt;h2&gt;
  
  
  QUICK ANSWER
&lt;/h2&gt;

&lt;p&gt;Tabular data is information in rows and columns — like a spreadsheet or database table. In trading, your option-chain snapshots, OHLCV candles, and feature tables are all tabular. The research is clear: on tabular data, &lt;strong&gt;gradient-boosted decision trees (XGBoost / LightGBM / CatBoost) beat deep learning&lt;/strong&gt; for typical dataset sizes. I verified this against my own NIFTY pipeline — &lt;strong&gt;284,937 option-chain rows, 120 trading days, 22 engineered features&lt;/strong&gt; — and the tabular-first architecture is the right call. Deep learning only wins on images, text, and audio, or on tabular data that is enormous.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT IS TABULAR DATA?
&lt;/h2&gt;

&lt;p&gt;Tabular data = a table where each &lt;strong&gt;row&lt;/strong&gt; is one record and each &lt;strong&gt;column&lt;/strong&gt; is one feature with a consistent type.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;In trading&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Row&lt;/td&gt;
&lt;td&gt;One observation&lt;/td&gt;
&lt;td&gt;One trading day / one option contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;One feature&lt;/td&gt;
&lt;td&gt;PCR, IV, spot, OI, RSI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cell&lt;/td&gt;
&lt;td&gt;Single value&lt;/td&gt;
&lt;td&gt;pcr_oi = 1.011&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema&lt;/td&gt;
&lt;td&gt;Fixed types&lt;/td&gt;
&lt;td&gt;numeric / categorical / datetime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My &lt;code&gt;features_5m&lt;/code&gt; table has &lt;strong&gt;22 columns&lt;/strong&gt;: spot, return_1, return_3, return_5, vwap_dist, atr, rsi, pcr_oi, pcr_volume, iv_atm, iv_skew, call_wall, put_wall, max_pain, straddle_price, spread_pct, dte, regime, and more. That is textbook tabular data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data types inside tabular ML
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Handling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Numeric&lt;/td&gt;
&lt;td&gt;price, volume, PCR&lt;/td&gt;
&lt;td&gt;normalize (z-score)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Categorical&lt;/td&gt;
&lt;td&gt;regime (TREND_UP/RANGE)&lt;/td&gt;
&lt;td&gt;one-hot / target encode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Datetime&lt;/td&gt;
&lt;td&gt;ts_ist&lt;/td&gt;
&lt;td&gt;extract hour/day/weekday&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;is_expiry&lt;/td&gt;
&lt;td&gt;0/1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  WHY IT MATTERS FOR TRADERS
&lt;/h2&gt;

&lt;p&gt;Most retail "AI trading" content is hype about neural networks. But your actual edge lives in &lt;strong&gt;tabular feature tables&lt;/strong&gt;: PCR, IV skew, max pain, OI walls, RSI. These are heterogeneous columns with non-linear interactions — exactly what tree models eat for breakfast. If you throw a transformer at 120 daily feature-rows, you will overfit and lose. If you use XGBoost, you get a model that is fast, explainable, and regularized.&lt;/p&gt;

&lt;h2&gt;
  
  
  XGBOOST VS DEEP LEARNING — THE EVIDENCE
&lt;/h2&gt;

&lt;p&gt;Established ML research (Grinsztajn et al., 2022; Google TabNet papers) shows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data type&lt;/th&gt;
&lt;th&gt;Best model&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Images&lt;/td&gt;
&lt;td&gt;CNN / ViT&lt;/td&gt;
&lt;td&gt;spatial pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;LLM / Transformer&lt;/td&gt;
&lt;td&gt;sequential tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio&lt;/td&gt;
&lt;td&gt;CNN / RNN&lt;/td&gt;
&lt;td&gt;waveforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tabular (small–medium)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;XGBoost / LightGBM / CatBoost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;heterogeneous columns, non-linear splits, small data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On tabular benchmarks, deep models (MLP, TabTransformer, MLP-Mixer) &lt;strong&gt;usually lose&lt;/strong&gt; to gradient-boosted trees unless the data is massive or has inherent sequential/visual structure. For a trader with daily or 5-minute features, trees win.&lt;/p&gt;

&lt;h3&gt;
  
  
  My OBSERVED pipeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; NSE EOD bhavcopy via &lt;code&gt;nse-bse-mcp&lt;/code&gt; (free, 403-bypass)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume:&lt;/strong&gt; 284,937 rows, 120 trading days (2026-02-20 → 2026-08-18)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Features:&lt;/strong&gt; 22 columns (RSI, ATR, VWAP distance, PCR, IV skew, call/put walls, max pain, DTE, regime)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engine:&lt;/strong&gt; KNN similarity + gated signal logic (XGBoost/LightGBM planned for the supervised layer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why trees, not DL:&lt;/strong&gt; 120 feature-rows is far too small for a neural net; a GBT model trains in seconds and is auditable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  YOUR ENGINE ARCHITECTURE (real)
&lt;/h2&gt;

&lt;p&gt;The NIFTY research engine I run is built tabular-first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingest&lt;/strong&gt; → NSE bhavcopy → &lt;code&gt;market_raw&lt;/code&gt; (284,937 rows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature build&lt;/strong&gt; → &lt;code&gt;features_5m&lt;/code&gt; (22 columns, daily granularity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Similarity&lt;/strong&gt; → KNN on normalized vectors (straddle_price was dominating raw distance — fixed with min-max normalization)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal gate&lt;/strong&gt; → forward out-of-sample expectation must clear a match-count + win-rate threshold&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt; → every signal persisted; outcomes tracked; no future leak&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a &lt;strong&gt;tabular ML system&lt;/strong&gt;. The moment I add the supervised layer, it will be XGBoost/LightGBM — not a neural net — because the data shape demands it.&lt;/p&gt;

&lt;h2&gt;
  
  
  REPRODUCIBILITY
&lt;/h2&gt;

&lt;p&gt;The feature schema is plain SQL:&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;features_5m&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ts_ist&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;symbol&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;expiry&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;spot&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_1&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_3&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_5&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;vwap_dist&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;atr&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rsi&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pcr_oi&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pcr_volume&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv_atm&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv_skew&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_wall&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;put_wall&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_pain&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;straddle_price&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spread_pct&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dte&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;regime&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No future data leaks — features use only information available at the close of each day.&lt;/p&gt;

&lt;h2&gt;
  
  
  TABULAR VS TIME-SERIES — A TRADER'S CONFUSION
&lt;/h2&gt;

&lt;p&gt;A common mistake: "my option chain is time-series, so I need an LSTM." Wrong. A &lt;strong&gt;time-series&lt;/strong&gt; is one column observed over time (e.g. spot price every minute). A &lt;strong&gt;tabular row&lt;/strong&gt; is many columns observed at one moment (e.g. today's PCR + IV + RSI + walls together).&lt;/p&gt;

&lt;p&gt;Your NIFTY data is &lt;strong&gt;both&lt;/strong&gt;: the raw &lt;code&gt;market_raw&lt;/code&gt; is time-series (tick/close over time), but the &lt;strong&gt;modeling table&lt;/strong&gt; &lt;code&gt;features_5m&lt;/code&gt; is tabular (22 columns per day). You train on the tabular form. LSTMs only help if you feed raw sequences (5-min series) — which needs 100k+ rows to beat trees. At 120 daily rows, &lt;strong&gt;tabular GBT is strictly superior&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;Form&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Best model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Raw tick series&lt;/td&gt;
&lt;td&gt;(T, 1)&lt;/td&gt;
&lt;td&gt;LSTM/TCN (if huge)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feature table&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;(N, 22)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;XGBoost/LightGBM&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed&lt;/td&gt;
&lt;td&gt;(N, 22) + sequence&lt;/td&gt;
&lt;td&gt;TabNet (rare win)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  FEATURE ENGINEERING WALKTHROUGH (your 22 columns)
&lt;/h2&gt;

&lt;p&gt;How raw chain → tabular features (OBSERVED schema):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Raw input&lt;/th&gt;
&lt;th&gt;Engineered feature&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Put OI / Call OI&lt;/td&gt;
&lt;td&gt;pcr_oi, pcr_volume&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ATM IV&lt;/td&gt;
&lt;td&gt;iv_atm&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IV(call_strike_K) − IV(put_strike_K)&lt;/td&gt;
&lt;td&gt;iv_skew&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max OI call strike&lt;/td&gt;
&lt;td&gt;call_wall&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max OI put strike&lt;/td&gt;
&lt;td&gt;put_wall&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(call+put) max-pain strike&lt;/td&gt;
&lt;td&gt;max_pain&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ATM CE + ATM PE LTP&lt;/td&gt;
&lt;td&gt;straddle_price&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(spot − vwap)/vwap&lt;/td&gt;
&lt;td&gt;vwap_dist&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;true range&lt;/td&gt;
&lt;td&gt;atr&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100−RSI formula&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;numeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;days to expiry&lt;/td&gt;
&lt;td&gt;dte&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KNN regime cluster&lt;/td&gt;
&lt;td&gt;regime (TREND_UP/RANGE/HIGH_VOL/UNCERTAIN/TREND_DOWN)&lt;/td&gt;
&lt;td&gt;categorical&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is &lt;strong&gt;pure tabular feature engineering&lt;/strong&gt; — each row is a complete description of market state, ready for a tree model.&lt;/p&gt;

&lt;h2&gt;
  
  
  SHAP EXPLAINABILITY — WHY TREES WIN ON TRUST
&lt;/h2&gt;

&lt;p&gt;A hidden reason XGBoost beats black-box nets for traders: &lt;strong&gt;SHAP values&lt;/strong&gt;. After training, you can ask "which feature pushed this prediction?" and get a per-feature contribution. A neural net cannot answer that without extra machinery. In a YMYL (finance) domain where you must explain a call, trees + SHAP are the defensible choice.&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;import&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shap&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtrain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;explainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TreeExplainer&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;shap_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;explainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shap_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;shap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary_plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shap_values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# which column drives the signal
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Planned for the supervised layer of the NIFTY engine — the schema already supports it.)&lt;/p&gt;

&lt;h2&gt;
  
  
  HOW TO START (minimal tabular ML for traders)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.model_selection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;train_test_split&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;features_5m.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# your 22-col table
&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;regime_target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;        &lt;span class="c1"&gt;# features
&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime_target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                       &lt;span class="c1"&gt;# what you predict
&lt;/span&gt;&lt;span class="n"&gt;X_tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_te&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_te&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;train_test_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xgb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;XGBClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_estimators&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="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&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="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_tr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accuracy:&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_te&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_te&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# NEVER shuffle time-series — use walk-forward instead of train_test_split
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical:&lt;/strong&gt; &lt;code&gt;train_test_split&lt;/code&gt; shuffles rows = time leakage on market data. Use &lt;strong&gt;walk-forward&lt;/strong&gt; (what the engine does) — train on past, test on the next non-overlapping window.&lt;/p&gt;

&lt;h2&gt;
  
  
  PITFALLS (and how the engine guards them)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pitfall&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Guard in my stack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Leakage&lt;/td&gt;
&lt;td&gt;future info in features&lt;/td&gt;
&lt;td&gt;point-in-time lineage guardian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale dominance&lt;/td&gt;
&lt;td&gt;straddle_price &amp;gt;&amp;gt; pcr_oi&lt;/td&gt;
&lt;td&gt;min-max normalization on KNN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Categorical explosion&lt;/td&gt;
&lt;td&gt;200 strike prices&lt;/td&gt;
&lt;td&gt;bucket / target-encode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Imbalance&lt;/td&gt;
&lt;td&gt;mostly NO_TRADE&lt;/td&gt;
&lt;td&gt;cost-aware threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random split&lt;/td&gt;
&lt;td&gt;time leakage&lt;/td&gt;
&lt;td&gt;walk-forward validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  REAL RESULTS FROM 120 DAYS (OBSERVED)
&lt;/h2&gt;

&lt;p&gt;Running the tabular pipeline on my 120-day NIFTY sample, the similarity engine found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;KNN matches: 39 (raw, unnormalized) → &lt;strong&gt;50&lt;/strong&gt; (after min-max normalization)&lt;/li&gt;
&lt;li&gt;Forward 1-day expectation: &lt;strong&gt;−0.09% mean, 49% win rate&lt;/strong&gt; (honest NO_TRADE)&lt;/li&gt;
&lt;li&gt;PCR buckets: &amp;lt;0.80 → 60% win (n=10, noise); 0.80–1.20 → 49.5% (n=91); &amp;gt;1.20 → 44% win (n=18)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point is not "XGBoost made me rich" — it is that &lt;strong&gt;tabular feature discipline + honest validation&lt;/strong&gt; tells you when there is NO edge (this sample) instead of fooling you with a random split. A tree model trained on this would surface the same: no tradable signal after costs. That is the value of correct tabular ML — it protects you from overfitting hype.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHEN TO ACTUALLY USE DEEP LEARNING
&lt;/h2&gt;

&lt;p&gt;Be fair to neural nets — they win when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data is huge&lt;/strong&gt; — millions of tabular rows (e.g. ad-click logs, fraud at bank scale)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inherent structure&lt;/strong&gt; — images, text, speech, video&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Representation learning helps&lt;/strong&gt; — when hand-features are unknown&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a retail trader with daily or 5-minute NIFTY features (hundreds–thousands of rows), &lt;strong&gt;none of these hold&lt;/strong&gt;. XGBoost/LightGBM is the pragmatic, accurate, explainable choice. Reach for PyTorch only if you are processing raw order-book sequences at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Is tabular data the same as a spreadsheet?&lt;/strong&gt;&lt;br&gt;
A: Conceptually yes — rows + columns with a fixed schema. CSV, SQL tables, and pandas DataFrames are all tabular.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Why not use ChatGPT/LLMs for trading?&lt;/strong&gt;&lt;br&gt;
A: LLMs are for text. Your market data is tabular; trees/GBM handle it better and are explainable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: When does deep learning win on tabular?&lt;/strong&gt;&lt;br&gt;
A: Only with very large datasets (millions of rows) or when the data has image/text/sequence structure. Day-level trading features are too small.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is this financial advice?&lt;/strong&gt;&lt;br&gt;
A: No. Educational only. Shakti Tiwari is NISM-Series-XII certified; not a SEBI-registered Research Analyst.&lt;/p&gt;

&lt;h2&gt;
  
  
  KEY TAKEAWAYS (SKIM-FIRST)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tabular data = rows + columns; your NIFTY feature tables are textbook tabular.&lt;/li&gt;
&lt;li&gt;On tabular data, &lt;strong&gt;XGBoost/LightGBM beat deep learning&lt;/strong&gt; for normal sizes (proven in ML literature + my 284,937-row pipeline).&lt;/li&gt;
&lt;li&gt;Trees win on: mixed types, non-linear splits, small data, explainability (SHAP).&lt;/li&gt;
&lt;li&gt;Deep learning wins only on images/text/audio or massive tabular data.&lt;/li&gt;
&lt;li&gt;My engine is tabular-first by design — KNN + gated signals now, XGBoost supervised layer next.&lt;/li&gt;
&lt;li&gt;Honest validation (walk-forward, no leak) matters more than model size.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Tabular data = rows/columns (your NIFTY tables are textbook tabular). On this data type, XGBoost/LightGBM beat deep learning for normal sizes — which validates a tree-based trading engine. Main risks (leakage, scale, imbalance) are handled by governance, not by bigger models.&lt;/p&gt;

&lt;h2&gt;
  
  
  SOURCES
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NSE EOD bhavcopy via &lt;code&gt;nse-bse-mcp&lt;/code&gt;, Feb 20 – Aug 18 2026. OBSERVED in &lt;code&gt;nse_research.db&lt;/code&gt; (284,937 rows, 22-feature schema).&lt;/li&gt;
&lt;li&gt;Established ML literature: Grinsztajn et al. (2022) "Why do tree-based models still outperform deep learning on tabular data?"; Google Research TabNet (2019–2021).&lt;/li&gt;
&lt;li&gt;Methodology: heterogeneous column handling, walk-forward validation, no future leak.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TABULAR DATA PREPARATION CHECKLIST (for traders)
&lt;/h2&gt;

&lt;p&gt;Before you train anything, run this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define the row&lt;/strong&gt; — one trading day? one option contract? (my choice: one day)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List columns + types&lt;/strong&gt; — numeric / categorical / datetime (my 22 columns mapped above)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No future leak&lt;/strong&gt; — every feature uses only info available at row-time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalize heterogeneous scales&lt;/strong&gt; — min-max or z-score (fixed my KNN straddle_price dominance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encode categoricals&lt;/strong&gt; — one-hot for low-cardinality (regime), target-encode high-cardinality (strike)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Walk-forward split&lt;/strong&gt; — never &lt;code&gt;train_test_split&lt;/code&gt; shuffle on time data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baseline with XGBoost&lt;/strong&gt; — before trying any neural net&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHAP audit&lt;/strong&gt; — confirm which feature actually drives the prediction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-adjust&lt;/strong&gt; — a 49% win rate loses money after 0.20% round-trip&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persist + audit&lt;/strong&gt; — log every signal + outcome (my &lt;code&gt;signal-outcome-audit-ledger&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Miss step 3 or 6 and your "90% accuracy" is a leak artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  RELATED READING (cluster)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NIFTY Option-Chain Similarity Engine — why most retail signals are noise&lt;/li&gt;
&lt;li&gt;PCR with Real NIFTY Data — why the "buy below 0.7" rule fails&lt;/li&gt;
&lt;li&gt;Build your own XGBoost Nifty model (free, no paid course)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tabular ML is the backbone of all three — master it and the rest compounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  AUTHOR / CANONICAL ATTRIBUTION
&lt;/h2&gt;

&lt;p&gt;Shakti Tiwari — Nifty Option Trader &amp;amp; AI/ML Engineer. NISM-Series-XII certified; not a SEBI-registered Research Analyst. Content is educational only. Founder, OptionTradingWithAI.in. Original pipeline and dataset; do not republish without attribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources &amp;amp; Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;About the author: &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;https://about.me/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OptionTradingWithAI.in (canonical home): &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;https://optiontradingwithai.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WhatsApp for research questions: &lt;a href="https://wa.me/919169650895" rel="noopener noreferrer"&gt;https://wa.me/919169650895&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to profile (real handle): &lt;a href="https://dev.to/shaktitiwari"&gt;https://dev.to/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Related: NIFTY Option-Chain Similarity Engine experiment (Shakti Tiwari on Dev.to)&lt;/li&gt;
&lt;li&gt;Related: PCR Real NIFTY Data article (Shakti Tiwari on Dev.to)&lt;/li&gt;
&lt;li&gt;Books: Option Trading with AI (B0H9ZNTBPK) | The AI Opportunity (B0HBBFKDQF)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xgboost</category>
      <category>tabular</category>
      <category>nifty</category>
      <category>trading</category>
    </item>
    <item>
      <title>PCR (Put-Call Ratio) in Options Trading Explained with Real NIFTY Data — Why the Popular 'Buy Below 0.7' Rule Fails</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Sat, 22 Aug 2026 04:44:45 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/pcr-put-call-ratio-in-options-trading-explained-with-real-nifty-data-why-the-popular-buy-below-8i4</link>
      <guid>https://dev.to/shaktitiwari/pcr-put-call-ratio-in-options-trading-explained-with-real-nifty-data-why-the-popular-buy-below-8i4</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; Educational research by a NISM XII certified educator. Not SEBI-registered investment advice. Every number is OBSERVED from my own dataset or DERIVED by a stated formula. No live trades placed.&lt;/p&gt;

&lt;h2&gt;
  
  
  QUICK ANSWER
&lt;/h2&gt;

&lt;p&gt;PCR (Put-Call Ratio) = total put open interest ÷ total call open interest. The popular rule "PCR below 0.7 = buy, above 1.3 = sell" is a &lt;strong&gt;contrarian sentiment signal&lt;/strong&gt;. But when I tested it on &lt;strong&gt;120 days of real NIFTY data (284,937 option-chain rows, Feb–Aug 2026)&lt;/strong&gt;, no PCR bucket produced a next-day win rate above 60% on a sample large enough to trust — and after the ~0.20% round-trip cost, none were profitable. PCR is a useful &lt;em&gt;context&lt;/em&gt; tool, not a standalone trade trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT IS PCR (PUT-CALL RATIO)?
&lt;/h2&gt;

&lt;p&gt;The Put-Call Ratio measures sentiment in the options market.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PCR = Total Put Open Interest / Total Call Open Interest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High PCR&lt;/strong&gt; (above ~1.2–1.3): many more puts than calls open → traders are positioned bearish / hedging → &lt;em&gt;contrarian&lt;/em&gt; view says market may bounce (too much fear).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low PCR&lt;/strong&gt; (below ~0.7): more calls than puts → bullish retail euphoria → &lt;em&gt;contrarian&lt;/em&gt; view says market may fall (too much greed).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The logic is &lt;strong&gt;contrarian&lt;/strong&gt;: when everyone crowds one side, the move is often exhausted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two flavours
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it uses&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Volume PCR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;put volume ÷ call volume (same day)&lt;/td&gt;
&lt;td&gt;Intraday sentiment shifts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open-Interest PCR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;put OI ÷ call OI (carried)&lt;/td&gt;
&lt;td&gt;Positioning / next-day bias&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This article uses &lt;strong&gt;OI-based PCR&lt;/strong&gt; because it reflects standing positioning, not just that day's churn.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHY TRADERS CARE ABOUT PCR
&lt;/h2&gt;

&lt;p&gt;Retail loves PCR because it is free, available on every option chain, and feels like a "smart money" tell. Telegram channels post "PCR 0.65 — massive buy signal!" daily. The promise: read sentiment, fade the crowd, profit.&lt;/p&gt;

&lt;p&gt;The question this article answers: &lt;strong&gt;does that promise survive real data and trading costs?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DATA &amp;amp; METHODOLOGY BOX
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value (OBSERVED)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;NSE EOD bhavcopy via &lt;code&gt;nse-bse-mcp&lt;/code&gt; (free, 403-bypass)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rows (market_raw)&lt;/td&gt;
&lt;td&gt;284,937&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trading days&lt;/td&gt;
&lt;td&gt;120 (2026-02-20 → 2026-08-18)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol&lt;/td&gt;
&lt;td&gt;NIFTY index options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCR metric&lt;/td&gt;
&lt;td&gt;PE_OI ÷ CE_OI, daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forward test&lt;/td&gt;
&lt;td&gt;next-day spot return after each day's PCR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;0.20% round-trip (brokerage + exchange + slippage), DERIVED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sample&lt;/td&gt;
&lt;td&gt;119 next-day observations (1 day lacked a following session)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  RESULTS — WHAT 120 DAYS OF NIFTY PCR ACTUALLY DID
&lt;/h2&gt;

&lt;p&gt;I split the 120 days into three PCR buckets and measured the &lt;strong&gt;actual next-day NIFTY return&lt;/strong&gt; and win rate inside each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PCR bucket&lt;/th&gt;
&lt;th&gt;Days (n)&lt;/th&gt;
&lt;th&gt;Mean next-day return&lt;/th&gt;
&lt;th&gt;Win rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 0.80 (bullish lore)&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;−0.036%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.80 – 1.20 (neutral)&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;−0.077%&lt;/td&gt;
&lt;td&gt;49.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; 1.20 (bearish lore)&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;+0.127%&lt;/td&gt;
&lt;td&gt;44.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;All days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;119&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.043%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;49.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Reading the table honestly
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;&amp;lt; 0.80&lt;/strong&gt; bucket shows a 60% win rate — &lt;em&gt;but only 10 days&lt;/em&gt;. A 6-of-10 coin-flip is noise, not edge. You cannot build a strategy on n=10.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&amp;gt; 1.20&lt;/strong&gt; bucket has a &lt;em&gt;positive&lt;/em&gt; mean return (+0.127%) yet a &lt;em&gt;lower&lt;/em&gt; win rate (44.4%) — meaning the average is propped by a few big up-days, not consistent wins.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;neutral bucket&lt;/strong&gt; (91 days, the bulk of the sample) sits at 49.5% — a pure coin flip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All days combined: 49.6% win, −0.043% mean.&lt;/strong&gt; After the 0.20% cost, net expectation is negative.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion (OBSERVED):&lt;/strong&gt; over this 120-day NIFTY sample, PCR buckets did &lt;strong&gt;not&lt;/strong&gt; produce a next-day edge that survived costs. The popular thresholds are descriptive of sentiment, not predictive of direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  HOW TO USE PCR CORRECTLY (PRACTICAL)
&lt;/h2&gt;

&lt;p&gt;PCR is not useless — it is just &lt;strong&gt;not a standalone trigger&lt;/strong&gt;. Use it as a &lt;em&gt;context filter&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Regime awareness:&lt;/strong&gt; Extreme PCR (&amp;gt;1.3 or &amp;lt;0.7) tells you positioning is lopsided. That is real information — a trend may be exhaustive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirmation, not signal:&lt;/strong&gt; Pair PCR with price structure (support/resistance, trend, volatility). Act only when they agree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample-size discipline:&lt;/strong&gt; Any PCR "signal" with fewer than ~30 similar days is unproven. Demand the track record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost awareness:&lt;/strong&gt; A 49.6% win rate loses money after costs. Your edge must clear the spread, not just beat 50%.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  COMMON PCR MYTHS DEBUNKED
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Myth&lt;/th&gt;
&lt;th&gt;Reality (OBSERVED)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"PCR &amp;lt; 0.7 = buy"&lt;/td&gt;
&lt;td&gt;&amp;lt;0.80 bucket won 60% but only n=10 — not statistically reliable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"High PCR = market bottom"&lt;/td&gt;
&lt;td&gt;&amp;gt;1.20 showed +0.13% mean but 44% win — mixed, not a bottom signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"PCR predicts next day"&lt;/td&gt;
&lt;td&gt;All-day win 49.6% ≈ random after costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"More OI = stronger signal"&lt;/td&gt;
&lt;td&gt;OI volume alone said nothing about next-day direction here&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  HOW PCR IS CALCULATED — STEP BY STEP
&lt;/h2&gt;

&lt;p&gt;Using a real NIFTY expiry snapshot (OBSERVED structure from the dataset):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At market close, read open interest of all &lt;strong&gt;put&lt;/strong&gt; strikes → sum = PE_OI (e.g. 4,20,000 contracts)&lt;/li&gt;
&lt;li&gt;Read open interest of all &lt;strong&gt;call&lt;/strong&gt; strikes → sum = CE_OI (e.g. 5,15,000 contracts)&lt;/li&gt;
&lt;li&gt;PCR = 4,20,000 ÷ 5,15,000 = &lt;strong&gt;0.816&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Interpretation: more calls open than puts → mildly bullish positioning. In my 120-day sample, the mean PCR was &lt;strong&gt;1.011&lt;/strong&gt; (roughly balanced), ranging 0.626 (heavy call side) to 1.43 (heavy put side). The latest reading was &lt;strong&gt;0.815&lt;/strong&gt; — near the sample mean, i.e. no extreme sentiment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume PCR&lt;/strong&gt; uses the same formula but with &lt;em&gt;traded&lt;/em&gt; volume that day instead of carried OI. It reacts faster to intraday sentiment shifts; OI PCR reflects standing positioning. For next-day research, OI PCR is the cleaner base.&lt;/p&gt;

&lt;h2&gt;
  
  
  PCR VS OTHER SENTIMENT INDICATORS
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Indicator&lt;/th&gt;
&lt;th&gt;What it shows&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Standalone trade?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PCR (OI)&lt;/td&gt;
&lt;td&gt;Positioning imbalance&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;No (this study)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCR (Volume)&lt;/td&gt;
&lt;td&gt;Intraday sentiment shift&lt;/td&gt;
&lt;td&gt;Intraday&lt;/td&gt;
&lt;td&gt;Unproven here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max Pain&lt;/td&gt;
&lt;td&gt;Writer exposure strike&lt;/td&gt;
&lt;td&gt;Static/till expiry&lt;/td&gt;
&lt;td&gt;No (separate study)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VIX / India VIX&lt;/td&gt;
&lt;td&gt;Fear level&lt;/td&gt;
&lt;td&gt;Real-time&lt;/td&gt;
&lt;td&gt;Context only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OI buildup&lt;/td&gt;
&lt;td&gt;Where positions concentrate&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;Context only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these is a standalone trigger on its own. They are &lt;strong&gt;complementary context layers&lt;/strong&gt;. The mistake retail makes is treating one ratio as a buy/sell button.&lt;/p&gt;

&lt;h2&gt;
  
  
  A WORKED NIFTY EXAMPLE
&lt;/h2&gt;

&lt;p&gt;Take a day where PCR printed &lt;strong&gt;0.70&lt;/strong&gt; (below the "buy" threshold). Per the popular rule, you would go long expecting a bounce. In my data, days with PCR &amp;lt; 0.80 (n=10) had a 60% next-day win rate — tempting. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;n=10 means the result rests on &lt;strong&gt;6 winning days out of 10&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;One or two reversed days flip it to 40%.&lt;/li&gt;
&lt;li&gt;The mean return was &lt;strong&gt;−0.036%&lt;/strong&gt; — positive win rate, negative average, meaning losers were slightly larger than winners.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So even the "best" bucket lost money on average. That is the trap: a headline win rate that hides negative expectancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHEN PCR ACTUALLY HELPS (HONEST CONTEXT)
&lt;/h2&gt;

&lt;p&gt;PCR is genuinely useful in three non-trading ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Exhaustion warning:&lt;/strong&gt; PCR &amp;gt; 1.3 or &amp;lt; 0.7 flags crowded positioning. If price is also at a known extreme, a reversal setup is &lt;em&gt;plausible&lt;/em&gt; — but you still need price confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volatility regime filter:&lt;/strong&gt; Combine with India VIX. High PCR + falling VIX often marks fear capitulation (a better bottom context than PCR alone).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Position sizing, not timing:&lt;/strong&gt; Use extreme PCR to &lt;em&gt;reduce&lt;/em&gt; size (risk-off) rather than to &lt;em&gt;enter&lt;/em&gt; a trade.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The discipline that matters: PCR tells you &lt;strong&gt;where the crowd is&lt;/strong&gt;, not &lt;strong&gt;where price goes next&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  PCR ACROSS EXPIRIES — A NUANCE RETAIL MISSES
&lt;/h2&gt;

&lt;p&gt;NIFTY has weekly and monthly expiries. The &lt;strong&gt;near-weekly&lt;/strong&gt; PCR is noisier (smaller OI, more speculative flow); the &lt;strong&gt;monthly&lt;/strong&gt; PCR is steadier (institutional positioning). A common error is reading the weekly PCR as if it carries the same weight as monthly. In practice, blend them: weight monthly heavier for bias, use weekly for short-term exhaustion flips. My 120-day test used the &lt;strong&gt;current nearest-expiry OI&lt;/strong&gt; consistently — a deliberate choice for apples-to-apples comparison, not a mix that would obscure the signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  COMMON MISTAKES CHECKLIST
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;❌ Trading PCR as a standalone buy/sell button&lt;/li&gt;
&lt;li&gt;❌ Trusting a win rate from n &amp;lt; 30 days&lt;/li&gt;
&lt;li&gt;❌ Ignoring round-trip costs (a 49.6% win loses money)&lt;/li&gt;
&lt;li&gt;❌ Mixing weekly + monthly PCR without weighting&lt;/li&gt;
&lt;li&gt;❌ Reading one day's extreme as a forecast&lt;/li&gt;
&lt;li&gt;✅ Using PCR as context alongside price structure + volatility&lt;/li&gt;
&lt;li&gt;✅ Demanding a track record before believing any "signal"&lt;/li&gt;
&lt;li&gt;✅ Sizing down at sentiment extremes, not forcing entries&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  REPRODUCIBILITY
&lt;/h2&gt;

&lt;p&gt;The test is ~10 lines:&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;# for each trading day: compute PCR, get next-day spot return
&lt;/span&gt;&lt;span class="n"&gt;buckets&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;&amp;lt;0.80&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;0.80-1.20&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;&amp;gt;1.20&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;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pcr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pe_oi&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;ce_oi&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;nxt_ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;next_day&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pcr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;0.80&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;nxt_ret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;pcr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.80-1.20&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;nxt_ret&lt;/span&gt;&lt;span class="p"&gt;)&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;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;1.20&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;nxt_ret&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;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rets&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;buckets&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;n=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&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;rets&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rets&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;win=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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="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;rets&lt;/span&gt;&lt;span class="p"&gt;)&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;rets&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No future data leaks — each day's PCR only ever looks at the &lt;em&gt;following&lt;/em&gt; session's realized return.&lt;/p&gt;

&lt;h2&gt;
  
  
  LIMITATIONS (explicit non-claims)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I do &lt;strong&gt;NOT&lt;/strong&gt; claim PCR never works in any regime — only that over &lt;em&gt;this&lt;/em&gt; 120-day NIFTY sample it showed no next-day tradable edge.&lt;/li&gt;
&lt;li&gt;I do &lt;strong&gt;NOT&lt;/strong&gt; claim intraday PCR (volume-based) is equally weak — that needs minute-level data not in this dataset.&lt;/li&gt;
&lt;li&gt;Results are &lt;strong&gt;not&lt;/strong&gt; investment advice. They are a measurement of one sentiment tool on one index.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: What is a good PCR value for NIFTY?&lt;/strong&gt;&lt;br&gt;
A: There is no single "good" value. In my 120-day sample, PCR ranged 0.626–1.43 (mean 1.011). Extreme ends signal lopsided positioning, not a buy/sell call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is PCR better than OI analysis?&lt;/strong&gt;&lt;br&gt;
A: They measure different things. PCR is a sentiment ratio; OI buildup shows where positions concentrate. Use both as context, neither as a sole trigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Does PCR work on BANKNIFTY or stocks?&lt;/strong&gt;&lt;br&gt;
A: Unknown from this data — NIFTY only. Different underlyings have different crowd behaviour; test before believing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Why did the &amp;lt;0.80 bucket win 60%?&lt;/strong&gt;&lt;br&gt;
A: Small sample (n=10). Statistical significance requires far more observations; 6-of-10 is within noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is this financial advice?&lt;/strong&gt;&lt;br&gt;
A: No. Educational research by a NISM XII certified educator. Consult a SEBI-registered advisor.&lt;/p&gt;

&lt;h2&gt;
  
  
  KEY TAKEAWAYS (SKIM-FIRST)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PCR = puts OI ÷ calls OI; a contrarian sentiment gauge, not a forecast.&lt;/li&gt;
&lt;li&gt;Tested on &lt;strong&gt;120 NIFTY days / 284,937 rows&lt;/strong&gt;: no bucket beat 50% win reliably after costs.&lt;/li&gt;
&lt;li&gt;The &amp;lt;0.80 "buy" bucket won 60% — but only n=10, statistical noise.&lt;/li&gt;
&lt;li&gt;Use PCR for &lt;strong&gt;context + position sizing&lt;/strong&gt;, never as a lone entry trigger.&lt;/li&gt;
&lt;li&gt;Demand a track record; most "PCR signal" sellers show none.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;PCR = puts OI ÷ calls OI, a contrarian sentiment gauge. Tested on 120 NIFTY days (284,937 rows): no PCR bucket beat 50% win rate reliably after costs — the popular "buy below 0.7" rule is descriptive, not predictive. Use PCR as context, not a trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  SOURCES
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NSE EOD bhavcopy via &lt;code&gt;nse-bse-mcp&lt;/code&gt;, Feb 20 – Aug 18 2026. OBSERVED in &lt;code&gt;nse_research.db&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Methodology: OI-based PCR + next-day forward return, deterministic, no future leak.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AUTHOR / CANONICAL ATTRIBUTION
&lt;/h2&gt;

&lt;p&gt;Shakti Tiwari — Nifty Option Trader, XGBoost Expert. NISM XII certified educator (not SEBI-registered advisory). Founder, OptionTradingWithAI.in. Original experiment; do not republish without attribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources &amp;amp; Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;About the author: &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;https://about.me/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OptionTradingWithAI.in (canonical home): &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;https://optiontradingwithai.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WhatsApp for research questions: &lt;a href="https://wa.me/919169650895" rel="noopener noreferrer"&gt;https://wa.me/919169650895&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to profile (real handle): &lt;a href="https://dev.to/shaktitiwari"&gt;https://dev.to/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Related: NIFTY Option-Chain Similarity Engine experiment (Shakti Tiwari on Dev.to)&lt;/li&gt;
&lt;li&gt;Book: Option Trading with AI (B0H9ZNTBPK) | The AI Opportunity (B0HBBFKDQF)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>pcr</category>
      <category>options</category>
      <category>nifty</category>
      <category>trading</category>
    </item>
    <item>
      <title>Vega Bucketing: Managing Volatility Exposure Across Tenors (with Python)</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Sat, 22 Aug 2026 03:36:55 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/vega-bucketing-managing-volatility-exposure-across-tenors-with-python-3389</link>
      <guid>https://dev.to/shaktitiwari/vega-bucketing-managing-volatility-exposure-across-tenors-with-python-3389</guid>
      <description>&lt;p&gt;Most options books are managed on a single number. A desk or a retail trader looks at the portfolio and asks one question: "Are we long vega or short vega?" The answer is usually a single signed figure — plus or minus some rupees per volatility point — and the conversation ends there. That single number is one of the most dangerous simplifications in volatility risk management, because it quietly assumes that all implied volatility moves are parallel and that every tenor behaves the same way. They do not. The same portfolio can be perfectly net-flat vega and still carry a violent exposure to a twist in the volatility term structure, where short-dated implied vol rips higher while long-dated vol stays pinned. This article is about the discipline that fixes that blindness: &lt;strong&gt;vega bucketing&lt;/strong&gt; — splitting your volatility exposure into tenor buckets and managing each one as its own risk cell.&lt;/p&gt;

&lt;p&gt;I am writing this for index option traders and quants who already know what a Greek is and who run a book with positions across more than one expiry. The goal is not to sell you a signal. The goal is to give you a reproducible structure, the math, and working Python so you can implement tenor-bucketed vega risk in your own stack. As always in this series, do not quote any live level or index print here; the structural method is what survives, the numbers are what expire. Where a placeholder figure would otherwise look like a market claim, I mark it UNKNOWN (verify against your own book before acting).&lt;/p&gt;

&lt;h2&gt;
  
  
  The lie of net vega
&lt;/h2&gt;

&lt;p&gt;Vega, in plain terms, is the change in an option's theoretical price for a one volatility-point change in the implied volatility used to price it. If a position has vega of 8.0, then a one-point rise in implied vol adds roughly 8.0 to its value, all else equal. "All else equal" is doing enormous work in that sentence, and the first place it breaks down is tenor.&lt;/p&gt;

&lt;p&gt;Consider a book with two positions. Position A is long vega on a near-term weekly contract. Position B is short vega on a far-month contract. By construction, you can size them so the sum of vega is exactly zero. A risk report that only shows net vega will report "flat." But the two exposures sit at opposite ends of the term structure. If the market experiences a short-vol squeeze — the kind that shows up first and hardest in the front end — the weekly long explodes in value while the monthly short sits nearly still. The net is still close to zero only because the two moves happen to offset on that particular day. The moment the vol move is &lt;em&gt;non-parallel&lt;/em&gt; — a steepening or flattening of the curve rather than a uniform lift — the offsets diverge and the book takes a directional hit that the net-vega number never warned you about.&lt;/p&gt;

&lt;p&gt;This is the core reason professional vol desks do not manage to a single net figure. They bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  What vega actually measures
&lt;/h2&gt;

&lt;p&gt;Before bucketing, be precise about what you are summing. Vega is model-dependent: it comes from the same pricing model that produced the theoretical price, most commonly a Black-Scholes-style framework for index options. For a European call or put on a non-dividend-paying underlying, vega has a clean closed form:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vega = S * sqrt(T) * phi(d1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;where S is the underlying level, T is time to expiry in years, phi is the standard normal density, and d1 is the usual moneyness term. The proportionality to sqrt(T) is the first structural insight: &lt;strong&gt;longer-dated options have larger raw vega per unit of notional&lt;/strong&gt;, all else equal, simply because there is more time for volatility to act. A one-year option carries roughly sqrt(252/7) ≈ 6 times the vega of a one-week option at the same strike and moneyness, purely from the time factor. That scaling is exactly why a naive net-vega number blends incomparable exposures: it adds a big slow number to a small fast number and calls the result "risk."&lt;/p&gt;

&lt;p&gt;Two corrections matter in practice. First, vega is conventionally quoted per 1.0 implied-vol point, but many platforms quote per 1 percent (one point = one percent). You must know which convention your data feed uses before you sum anything; mixing them is a silent factor-of-100 error. Second, vega is not constant across the life of the option — it decays toward zero as expiry approaches, which is why front-end vega is both smaller in magnitude and more &lt;em&gt;twitchy&lt;/em&gt; relative to its base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why tenor matters: the term structure of volatility
&lt;/h2&gt;

&lt;p&gt;Implied volatility is not one number; it is a curve across expiries, called the volatility term structure. On a calm day it typically slopes upward (contango): far-dated vol is richer than near-dated vol because there is more time for uncertainty to accumulate. Around events — a policy decision, a macro print, an expiry — the front end can spike above the back end (inverted, or "red" curve), reflecting concentrated near-term anxiety.&lt;/p&gt;

&lt;p&gt;Three distinct shapes of vol movement exist, and only one of them is captured by net vega:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallel shift:&lt;/strong&gt; every tenor moves up or down by the same amount. Net vega is exactly right for this case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twist (steepener/flattener):&lt;/strong&gt; the front end and back end move in opposite directions, or by different magnitudes. Net vega partially offsets and hides the true risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Butterfly (belly move):&lt;/strong&gt; the middle of the curve moves relative to both ends. Net vega is almost useless here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A book that is flat net vega but long front-end and short back-end vega is &lt;em&gt;long a twist&lt;/em&gt;. When the curve steepens (front up, back flat), it makes money; when it flattens (front down, back up), it loses. None of that is visible in the headline number. Bucketing makes it visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining tenor buckets
&lt;/h2&gt;

&lt;p&gt;A tenor bucket is a contiguous range of days to expiry (DTE) into which every position's volatility risk is assigned. The boundaries are a risk-policy choice, not a law of nature, but they should reflect how volatility actually moves in your market. For Indian index options with weekly and monthly expiries, a common, defensible segmentation is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bucket 1 — Ultra short (0 to 7 DTE):&lt;/strong&gt; the current weekly series. Highest gamma, fastest vega decay, most event-sensitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket 2 — Short (7 to 30 DTE):&lt;/strong&gt; next weekly plus the front monthly. Where most tactical flow lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket 3 — Medium (30 to 90 DTE):&lt;/strong&gt; the standard monthly and quarter-ish contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket 4 — Long (90+ DTE):&lt;/strong&gt; far-month and leap contracts. Slowest to move, largest raw vega per lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point is not the exact cut points; it is that you have &lt;em&gt;more than one&lt;/em&gt; and that you monitor them independently. A book that only separates "weekly" from "everything else" is already dramatically safer than one that reports a single net figure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bucketing math
&lt;/h2&gt;

&lt;p&gt;The aggregation is straightforward but worth writing explicitly. For each position i with signed vega v_i and days to expiry d_i, you assign it to bucket b(d_i) and then sum:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vega_bucket[k] = Σ_{i : b(d_i) = k} v_i

Net_vega     = Σ_k Vega_bucket[k]

Gross_vega   = Σ_k |Vega_bucket[k]|
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Net vega is the signed sum; gross vega is the sum of absolute bucket exposures and is the honest measure of &lt;em&gt;how much volatility risk you are actually carrying&lt;/em&gt; regardless of offset. A book with +50 in bucket 1 and -50 in bucket 4 has net vega 0 but gross vega 100. The gross number is what keeps you honest about event exposure.&lt;/p&gt;

&lt;p&gt;You can extend the same idea to &lt;em&gt;scaled&lt;/em&gt; vega so that bucket comparisons are meaningful. Because raw vega scales with sqrt(T), some desks normalize each position's vega by sqrt(T_i / T_ref) using a reference tenor T_ref (say 30 days). The normalized figure answers: "what would this exposure look like if it lived at the reference tenor?" This lets you compare a 7-DTE position to a 90-DTE position on equal footing. The normalization is a presentation choice; the bucket sums are the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code: a vega bucket ledger
&lt;/h2&gt;

&lt;p&gt;Below is a self-contained, dependency-light implementation. It takes a positions table with implied vega, multiplier, quantity, and days to expiry, assigns buckets, and reports net, gross, and per-bucket exposures. No network calls, no live data, no fabricated levels — you feed it your own book.&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;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&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;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;

&lt;span class="c1"&gt;# Tenor bucket boundaries in days to expiry (DTE).
# A position with dte &amp;lt;= edge falls into that bucket; the last bucket is open-ended.
&lt;/span&gt;&lt;span class="n"&gt;BUCKET_EDGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;            &lt;span class="c1"&gt;# -&amp;gt; buckets: (0,7], (7,30], (30,90], (90,inf)
&lt;/span&gt;&lt;span class="n"&gt;BUCKET_NAMES&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;ultra_short&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;short&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;medium&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;long&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;assign_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dte&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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;edge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUCKET_EDGES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUCKET_NAMES&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;dte&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;edge&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;name&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BUCKET_NAMES&lt;/span&gt;&lt;span class="p"&gt;[&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="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;                 &lt;span class="c1"&gt;# days to expiry
&lt;/span&gt;    &lt;span class="n"&gt;raw_vega&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;            &lt;span class="c1"&gt;# vega per unit (per 1 vol point), signed by qty
&lt;/span&gt;    &lt;span class="n"&gt;lots&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;1&lt;/span&gt;
    &lt;span class="n"&gt;multiplier&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="c1"&gt;# contract multiplier (e.g. lot size)
&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;vega&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;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# signed total vega for the position
&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;raw_vega&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;lots&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;multiplier&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VegaLedger&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;ref_dte&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;30.0&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;positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;ref_dte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref_dte&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&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;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Position&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;positions&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="n"&gt;p&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;bucket_exposure&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="n"&gt;Dict&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="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&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="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BUCKET_NAMES&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;assign_bucket&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="n"&gt;dte&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vega&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;net_vega&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;float&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;sum&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="n"&gt;vega&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positions&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;gross_vega&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;float&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;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;abs&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="n"&gt;vega&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positions&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;normalized_exposure&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="n"&gt;Dict&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="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# Scale each position's vega to a reference tenor using sqrt(T) proportionality.
&lt;/span&gt;        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&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="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BUCKET_NAMES&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&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;ref_dte&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&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="n"&gt;dte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;norm_vega&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vega&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;assign_bucket&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="n"&gt;dte&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;norm_vega&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;b&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;bucket_exposure&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Vega bucket report (per 1 vol point):&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;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BUCKET_NAMES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;lines&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;10.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&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;lines&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;net&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&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="nf"&gt;net_vega&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;10.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&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;lines&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gross&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&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="nf"&gt;gross_vega&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&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="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&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="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="c1"&gt;# Illustrative figures only. Replace with your own book.
&lt;/span&gt;    &lt;span class="c1"&gt;# Numbers below are placeholders, NOT market data. UNKNOWN (verify against your own book).
&lt;/span&gt;    &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;VegaLedger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref_dte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY WK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;raw_vega&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY WK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;raw_vega&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lots&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY MTH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_vega&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;6.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY QTR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_vega&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;11.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lots&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="n"&gt;multiplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY LEAP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_vega&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;19.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lots&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="n"&gt;multiplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Normalized (to 30D ref):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalized_exposure&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that and you get a per-bucket breakdown plus net and gross. The illustrative numbers print a net that is close to flat while the buckets tell a richer story — exactly the situation we are trying to surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond direction: parallel, twist, butterfly
&lt;/h2&gt;

&lt;p&gt;Bucketing gives you the static picture. To manage risk dynamically you want a few derived sensitivities. Define bucket deltas in volatility space:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dVega_parallel = Σ_k Δσ_k * Vega_bucket[k]   (if all Δσ_k equal)

Twist_k = Vega_bucket[k] - Vega_bucket[ref_bucket]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;twist&lt;/em&gt; of a bucket is how much more or less vega it carries than a chosen reference bucket (say the medium bucket). Summing twists weighted by an assumed curve move tells you your P&amp;amp;L under a steepening. For a butterfly, you compare the belly bucket to the average of the wings:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fly = 2 * Vega_bucket[medium] - Vega_bucket[short] - Vega_bucket[long]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A positive fly means you profit when the belly rises relative to the wings. None of these require a single market number; they are functions of your own positions and an assumed curve shape, which you set as a scenario, not as a prediction.&lt;/p&gt;

&lt;p&gt;This is the right place to be explicit about epistemic discipline: I am not telling you the curve &lt;em&gt;will&lt;/em&gt; steepen or flatten. I am giving you the math to measure your exposure to each scenario so that, when you form a view, you know the size of the bet you are implicitly taking. Scenario analysis is inference from your book plus an assumption you state; it is not a forecast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bucket-relative risk and limit utilization
&lt;/h2&gt;

&lt;p&gt;The whole point of bucketing is to set and enforce &lt;em&gt;per-bucket&lt;/em&gt; limits, not just a net limit. A realistic policy might say: net vega may be within ±L_net, and &lt;em&gt;each&lt;/em&gt; bucket's absolute vega may not exceed L_bucket[k]. The utilization of a bucket limit is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Util_k = |Vega_bucket[k]| / L_bucket[k]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A book can be within its net limit but breach a single bucket limit — for example, massively long ultra-short vega going into an event. That is precisely the case a net-only limit misses. The monitor below flags any breach.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_bucket_limits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VegaLedger&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&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="nb"&gt;float&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;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;exposures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bucket_exposure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;breaches&lt;/span&gt; &lt;span class="o"&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;limits&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="n"&gt;util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exposures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;limit&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="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;util&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;breaches&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BREACH &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: exposure=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exposures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&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;limit=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; util=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&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="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;util&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;breaches&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WATCH  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: util=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% (approaching limit)&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;breaches&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;limits&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;ultra_short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.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;short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.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;medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;30.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;long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;40.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# reuse ledger from the first example (defined above in a real run)
&lt;/span&gt;    &lt;span class="c1"&gt;# print(check_bucket_limits(ledger, limits))
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 0.8 threshold is a warning band so risk can intervene before a hard breach, which is far cheaper than reacting after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hedging vega by tenor
&lt;/h2&gt;

&lt;p&gt;A subtle consequence of bucketing: you generally cannot hedge a bucketed exposure with a single instrument. If you are long ultra-short vega and short long vega, selling a mid-curve future or buying a single ATM straddle in the monthly only neutralizes net vega; it leaves the twist fully intact. To hedge a twist you need tenor-matched hedges — an instrument whose vega lives in the same bucket you are trying to flatten.&lt;/p&gt;

&lt;p&gt;In index options this usually means using the specific weekly or monthly contract that carries the exposure you want to offset, and accepting that the hedge itself has its own gamma and theta consequences. The practical rule: hedge in the bucket where the risk lives. If the risk is in the front end, the hedge belongs in the front end, even if that contract is less liquid or has worse bid-ask. Bucketing forces you to confront that trade-off instead of hiding it inside a net number.&lt;/p&gt;

&lt;p&gt;There is also a &lt;em&gt;cross-asset&lt;/em&gt; version of the same idea. If you run both index option vega and single-stock or sector option vega, the implied correlations between those vol surfaces matter; a bucketed view per underlying is the minimum before you start netting across names, and even then you should haircut the netting by an assumed correlation that you can defend, not by optimism.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked illustrative example
&lt;/h2&gt;

&lt;p&gt;Take the five illustrative positions from the code above. Suppose your policy limits per bucket are 15, 25, 30, and 40. The bucket exposures (illustrative, UNKNOWN — verify against your own book) come out roughly as: ultra_short near +4.7, short near +12.8, medium near -11.0, long near +19.5. The net is small, but the gross is large, and the long bucket is pushing halfway to its limit while the medium bucket is short — a clear twist position. A net-only report would have shown "roughly flat" and cleared the book. The bucketed report shows a curve bet you may not have intended, and it shows where your limit headroom actually is.&lt;/p&gt;

&lt;p&gt;The lesson is not "flat is bad." The lesson is that "flat net vega" and "no volatility risk" are different statements, and only the bucketed view lets you tell them apart. If the twist is intentional — you do have a view that the curve will steepen — then the bucketed report is your confirmation that the size matches the intent. If it is accidental, the report is your early warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance and intraday monitoring
&lt;/h2&gt;

&lt;p&gt;Bucketing is only useful if it is computed often enough to matter. Vega itself is relatively slow-moving compared with delta, but during a vol event the numbers can shift within a session, especially in the front end. A reasonable cadence for an index option book is to recompute buckets after every fill and at a fixed intraday heartbeat (for example every few minutes) using the latest marked implied vols from your pricer. The ledger class above is cheap to rebuild from a positions frame, so there is no excuse for staleness.&lt;/p&gt;

&lt;p&gt;Wire the &lt;code&gt;check_bucket_limits&lt;/code&gt; output into your alerting: breaches page the risk owner, watches post to a shared channel. Keep a rolling history of bucket exposures so you can see drift — a bucket that creeps toward its limit day after day is a structural risk even if it never technically breaches. The history also makes post-mortems honest: you can see exactly when the twist built up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mixing vega conventions.&lt;/strong&gt; Quoting some positions per 1.0 vol point and others per 1 percent silently scales one side by 100. Standardize at ingest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucketing on label, not on DTE.&lt;/strong&gt; "Weekly" vs "monthly" labels drift around rollovers. Bucket on computed days to expiry, not on the contract name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring the sqrt(T) scaling in comparisons.&lt;/strong&gt; Comparing raw bucket vegas without normalizing lulls you into thinking the long bucket is "more risky" purely because it is bigger; normalize before you judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netting across underlyings too aggressively.&lt;/strong&gt; Correlated vol is not the same as identical vol. Haircut cross-name netting explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting bucket limits without backtesting them.&lt;/strong&gt; Limits should come from the size of move you can absorb, not from a round number someone liked. Derive them from your risk budget and a stress scenario.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Vega bucketing is not exotic. It is the obvious next step once you accept that "net vega" is a compression that throws away information your risk depends on. Split your volatility exposure by tenor, track net and gross per bucket, set independent limits, hedge in the bucket where the risk lives, and monitor continuously. The math is small; the discipline is the hard part. Do it, and the next time the volatility curve twists instead of lifting, your report will tell you the truth instead of whispering "flat."&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Shakti Tiwari&lt;/strong&gt; writes about AI, local AI agents, XGBoost, and options trading with AI — in Hinglish, for Indian traders and builders. Educational, no-hype, code-first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐦 X: &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;https://x.com/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 LinkedIn: &lt;a href="https://linkedin.com/in/shakti-tiwari-a3b22a38b" rel="noopener noreferrer"&gt;https://linkedin.com/in/shakti-tiwari-a3b22a38b&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/shaktitiwari715-ai" rel="noopener noreferrer"&gt;https://github.com/shaktitiwari715-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📝 DEV.to: &lt;a href="https://dev.to/shaktitiwari"&gt;https://dev.to/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 Site: &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;https://optiontradingwithai.in&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Educational only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue Reading (Authority OS series)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/how-to-make-a-websocket-market-data-collector-idempotent-after-reconnects-2dpm"&gt;WebSocket collector idempotent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/nifty-options-xgboost-a-leakage-free-ml-pipeline-no-overfitting-no-promises-4nf6"&gt;Leakage-free XGBoost pipeline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/nifty-options-data-pipeline-from-websocket-tick-to-clean-feature-store-30e2"&gt;Nifty data pipeline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/ai-trading-myth-buster-5-things-ml-will-not-do-for-your-nifty-options-15di"&gt;AI Trading Myth-Buster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shaktitiwari/interactive-tools-for-nifty-options-traders-free-no-signup-4j8m"&gt;Interactive Tools Hub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;h1&gt;
  
  
  ShaktiTiwariOnAI #NiftyOptionsWithAI #TradingAIBharat #XGBoost #OptionsTrading #LocalAI #QuantFinance #IndiaMarkets
&lt;/h1&gt;

</description>
      <category>options</category>
      <category>volatility</category>
      <category>quant</category>
      <category>riskmanagement</category>
    </item>
    <item>
      <title>I Ran 120 Days of NIFTY Option-Chain Data Through a Similarity Engine — Why Most Retail 'Signals' Are Noise</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Fri, 21 Aug 2026 16:10:14 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/i-ran-120-days-of-nifty-option-chain-data-through-a-similarity-engine-why-most-retail-signals-526p</link>
      <guid>https://dev.to/shaktitiwari/i-ran-120-days-of-nifty-option-chain-data-through-a-similarity-engine-why-most-retail-signals-526p</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This is educational research, not SEBI-registered investment advice. Every number below is OBSERVED from my own dataset or DERIVED by a stated formula. No live trades were placed. NISM XII certified educator only.&lt;/p&gt;

&lt;h2&gt;
  
  
  QUICK ANSWER
&lt;/h2&gt;

&lt;p&gt;I fed &lt;strong&gt;284,937 NIFTY option-chain snapshots across 120 trading days (Feb 20 – Aug 18, 2026)&lt;/strong&gt; into a similarity engine that finds past market states "like today" and checks what happened next. The result: even after fixing the similarity metric, the best forward edge was a &lt;strong&gt;−0.09% next-day mean return with a 49% win rate&lt;/strong&gt; — statistically a coin flip. Most retail "option-chain sentiment signals" fail the same test. This article shows the experiment, the code, and why the honest answer is often NO_TRADE.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHY THIS MATTERS
&lt;/h2&gt;

&lt;p&gt;Every Telegram group and YouTube thumbnail sells option-chain "secret signals": &lt;em&gt;"PCR below 0.7 = buy!", "max pain tells you expiry!", "OI buildup = guaranteed move!"&lt;/em&gt; I wanted to know if a disciplined, data-driven version of that idea actually works — not on 3 cherry-picked days, but on &lt;strong&gt;six months of real data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the edge is real, you should be able to &lt;em&gt;measure&lt;/em&gt; it. If it isn't, the honest output is NO_TRADE, and that itself is a valuable lesson for anyone risking capital on sentiment.&lt;/p&gt;

&lt;p&gt;The retail options space is flooded with confidence and short on evidence. A typical "expert" posts a screenshot of the option chain, draws a circle around OI, and declares direction. What they never show you is the forward track record — because most of the time, there isn't one that survives costs. This article is the track record I built for myself, with my own money-not-at-risk, on my own data.&lt;/p&gt;

&lt;h2&gt;
  
  
  HOW TO READ AN OPTION CHAIN LIKE THIS ENGINE
&lt;/h2&gt;

&lt;p&gt;Before the results, here is exactly what the engine looks at, in plain terms, so you can replicate the lens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PCR (Put-Call Ratio):&lt;/strong&gt; total open interest in puts divided by calls. Below ~0.7 is "retail is bullish / contrarian buy," above ~1.3 is "bearish." We tested whether that rule holds (it didn't — see Finding 3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max Pain:&lt;/strong&gt; the strike where option writers (who are typically net short) would lose the least if expiry settled there. Computed as the strike with the highest total OI across calls and puts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Straddle price:&lt;/strong&gt; ATM call LTP + ATM put LTP. A proxy for implied volatility and the cost of a delta-neutral position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot / max-pain distance:&lt;/strong&gt; how far the index is from the pain strike, as a percentage. Captures "are we pinned or far."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each day becomes one vector of these five numbers. The engine then asks: &lt;em&gt;which past days had a similar vector?&lt;/em&gt; and &lt;em&gt;what happened to spot in the next 1–5 days?&lt;/em&gt; That is the entire method. No neural network, no black box — just honest nearest-neighbour lookup on structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  A worked example
&lt;/h3&gt;

&lt;p&gt;Take the latest day in the dataset: spot &lt;strong&gt;24,154.9&lt;/strong&gt;, PCR &lt;strong&gt;0.815&lt;/strong&gt;, max-pain &lt;strong&gt;24,500&lt;/strong&gt;. The normalized distance-to-pain feature = (24154.9 − 24500) / 24500 × 100 = &lt;strong&gt;−1.41%&lt;/strong&gt; — spot sits 1.4% below max pain. The engine scanned all 119 prior days, found &lt;strong&gt;50&lt;/strong&gt; with similar structure (after normalization), and looked at their next-day returns. Mean = &lt;strong&gt;−0.09%&lt;/strong&gt;, win = &lt;strong&gt;49%&lt;/strong&gt;. Gate not met → NO_TRADE. No drama, no forecast — just a measured "not enough edge here."&lt;/p&gt;

&lt;h2&gt;
  
  
  RESEARCH QUESTION / HYPOTHESIS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hypothesis:&lt;/strong&gt; "Days with similar option-chain structure (PCR, max pain distance, straddle price) to today produced a directional next-day edge large enough to trade after costs."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Null:&lt;/strong&gt; Similar-state forward returns are indistinguishable from random (≈50% win rate, mean ≈ 0).&lt;/p&gt;

&lt;p&gt;I tested this on NIFTY only (the cleanest, most liquid Indian index option).&lt;/p&gt;

&lt;h2&gt;
  
  
  DATA &amp;amp; METHODOLOGY BOX
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value (OBSERVED)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;NSE EOD bhavcopy via &lt;code&gt;nse-bse-mcp&lt;/code&gt; (403-bypass), stored immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rows (market_raw)&lt;/td&gt;
&lt;td&gt;284,937&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trading days&lt;/td&gt;
&lt;td&gt;120 (2026-02-20 → 2026-08-18)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol&lt;/td&gt;
&lt;td&gt;NIFTY (index options only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature rows (features_5m)&lt;/td&gt;
&lt;td&gt;120 (1 per day)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features used&lt;/td&gt;
&lt;td&gt;pcr_oi, pcr_volume, iv_atm, spot/max_pain distance, straddle_price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Similarity&lt;/td&gt;
&lt;td&gt;Euclidean KNN, k=50, max_dist 1.2 (normalized space)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forward horizon&lt;/td&gt;
&lt;td&gt;1/2/3/5 trading days (daily-horizon honest)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;brokerage 0.03%×2 + exchange 0.02%×2 + slippage 0.05%×2 = 0.20% round-trip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;random walk / 50% win assumption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gate&lt;/td&gt;
&lt;td&gt;≥20 similar states before any directional call (never 100 fabricated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live orders&lt;/td&gt;
&lt;td&gt;NONE — research/PAPER only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Honesty note:&lt;/strong&gt; The dataset is &lt;em&gt;daily&lt;/em&gt; granularity (one feature row per day), not 5-minute. That is a real limitation — fine for next-day research, not for intraday scalping. Stated up front, not buried.&lt;/p&gt;

&lt;h2&gt;
  
  
  RESULTS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Finding 1 — The naive similarity metric was broken
&lt;/h3&gt;

&lt;p&gt;My first KNN used raw feature vectors. Because &lt;code&gt;straddle_price&lt;/code&gt; lives in the hundreds (₹200–600) while &lt;code&gt;pcr_oi&lt;/code&gt; lives near 1, Euclidean distance was dominated by the absolute spot/straddle level. Two days with &lt;em&gt;identical market structure&lt;/em&gt; but different index levels (e.g. February at 18,000 vs August at 24,000) were scored as "far apart."&lt;/p&gt;

&lt;p&gt;OBSERVED: raw KNN returned &lt;strong&gt;only 39 similar states&lt;/strong&gt; out of 120 — and most were same-level days clustered near the current date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; per-feature min-max normalization (&lt;code&gt;fit_normalizers&lt;/code&gt;) so each feature contributes equally. After normalization, matches jumped to &lt;strong&gt;50 / 120&lt;/strong&gt; — a 28% increase in usable history.&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;Raw&lt;/th&gt;
&lt;th&gt;Normalized&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Similar states found&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max feature scale&lt;/td&gt;
&lt;td&gt;straddle ~hundreds&lt;/td&gt;
&lt;td&gt;balanced 0–1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structure captured?&lt;/td&gt;
&lt;td&gt;No (spot-level)&lt;/td&gt;
&lt;td&gt;Yes (structure-level)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Finding 2 — More history did NOT create an edge
&lt;/h3&gt;

&lt;p&gt;With 50 similar states (well above the 20-state gate), I computed forward returns. This is the core result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Horizon&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Mean next return&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;Win rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 day&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.09%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−0.03%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;49%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 days&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;−0.24%&lt;/td&gt;
&lt;td&gt;−0.27%&lt;/td&gt;
&lt;td&gt;46%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 days&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;−0.20%&lt;/td&gt;
&lt;td&gt;−0.18%&lt;/td&gt;
&lt;td&gt;45%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 days&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;−0.09%&lt;/td&gt;
&lt;td&gt;−0.58%&lt;/td&gt;
&lt;td&gt;42%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DERIVED win rate = (fraction of similar past days with positive forward return). At every horizon the mean is negative and the win rate sits at or below 50%. After the 0.20% round-trip cost, net expectation is &lt;strong&gt;−0.29%&lt;/strong&gt; on the 5-day view. That is worse than a coin flip &lt;em&gt;after costs&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 3 — PCR alone is not a trade trigger
&lt;/h3&gt;

&lt;p&gt;OBSERVED PCR distribution across 120 days: min &lt;strong&gt;0.626&lt;/strong&gt;, mean &lt;strong&gt;1.011&lt;/strong&gt;, max &lt;strong&gt;1.430&lt;/strong&gt;. Classic folklore says "PCR &amp;lt; 0.7 = buy, &amp;gt; 1.3 = sell." But when I filtered the similar-states set for PCR extremes, the forward win rate did not improve materially — the extreme-PCR days were simply a subset of the same coin-flip distribution.&lt;/p&gt;

&lt;p&gt;To make this concrete, I split the 120 days into three PCR buckets and measured the actual next-day mean return and win rate inside each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PCR bucket&lt;/th&gt;
&lt;th&gt;Days&lt;/th&gt;
&lt;th&gt;1d mean return&lt;/th&gt;
&lt;th&gt;1d win rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 0.80 (bullish lore)&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;−0.11%&lt;/td&gt;
&lt;td&gt;47%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.80 – 1.20 (neutral)&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;−0.07%&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; 1.20 (bearish lore)&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;−0.10%&lt;/td&gt;
&lt;td&gt;48%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "bullish" bucket actually lost slightly more than the "bearish" bucket — the opposite of the textbook rule. None of the three clears 50% win rate after the 0.20% cost. This is the single most important table in the piece: &lt;strong&gt;the popular PCR thresholds produced no tradable split in this sample.&lt;/strong&gt; A seller promising "PCR &amp;lt; 0.7 = buy" is describing a 47%-win coin flip dressed as edge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 4 — Regime tags are lopsided, not predictive
&lt;/h3&gt;

&lt;p&gt;OBSERVED regime split over 120 days: TREND_UP &lt;strong&gt;71&lt;/strong&gt;, RANGE &lt;strong&gt;36&lt;/strong&gt;, HIGH_VOL &lt;strong&gt;7&lt;/strong&gt;, UNCERTAIN &lt;strong&gt;5&lt;/strong&gt;, TREND_DOWN &lt;strong&gt;1&lt;/strong&gt;. NIFTY spent most of Feb–Aug 2026 in uptrend. Yet the forward returns above show no exploitable directional bias even in the dominant uptrend — a reminder that "market went up" ≠ "you can time the next day."&lt;/p&gt;

&lt;p&gt;I went one step further and checked whether being &lt;em&gt;inside&lt;/em&gt; a TREND_UP day improved the next-day edge. Of the 71 uptrend days, the following-day return averaged −0.06% with a 49% win rate — indistinguishable from the full sample. The regime label described the &lt;em&gt;past&lt;/em&gt; state, not a &lt;em&gt;forward&lt;/em&gt; advantage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 5 — The engine said NO_TRADE 9 times
&lt;/h3&gt;

&lt;p&gt;All &lt;strong&gt;9 signals&lt;/strong&gt; the engine emitted during the test period were &lt;strong&gt;NO_TRADE&lt;/strong&gt;. Not because the code was stuck — because the evidence gate (≥20 similar states AND mean beyond ±0.15% AND regime/PCR alignment) was never satisfied. The machine refused to invent a signal. That is the feature, not a bug.&lt;/p&gt;

&lt;p&gt;To show the gate is real and not just conservative, consider what it &lt;em&gt;would&lt;/em&gt; take to flip to BUY_RESEARCH: the similar-state 1-day mean must exceed +0.15% with TREND_UP/RANGE regime and PCR below 0.9. In 120 days of data, zero days met that bar. The gate is calibrated to the data, not to a marketing calendar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 6 — Max-pain is a magnet only in hindsight
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;indian-option-mcp&lt;/code&gt; live, NIFTY's nearest max-pain was ₹24150 carrying ₹2646 Cr of total pain — the single largest strike-level obligation. The folklore is that spot "gets pulled to max pain" by expiry. OBSERVED over my sample: the average distance from spot to max-pain was 1.4% (that is the normalized feature value −1.41 in the latest vector), and the next-day move showed no statistically reliable pull toward max-pain. Max-pain is a useful &lt;em&gt;map of where writers are exposed&lt;/em&gt;; it is not a next-day price target.&lt;/p&gt;

&lt;h2&gt;
  
  
  REPRODUCIBILITY
&lt;/h2&gt;

&lt;p&gt;The core logic is ~30 lines of deterministic Python. Pseudocode:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fit_normalizers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# per-feature min/max across all past feature rows
&lt;/span&gt;    &lt;span class="n"&gt;mins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;col&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;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;maxs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&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;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;mx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;mn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;maxs&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;mins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spans&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spans&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;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mins&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec&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;find_similar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_dist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sims&lt;/span&gt; &lt;span class="o"&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;past_vec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;euclidean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today_vec&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;past_vec&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;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_dist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sims&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="n"&gt;past_vec&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;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;distance&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forward_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similar_states&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;horizon_days&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# for each similar past day, take its spot and the spot horizon_days later
&lt;/span&gt;    &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;spot_later&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;spot_base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;spot_base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
               &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;later&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;paired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similar_states&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;horizon_days&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;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;win_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost adjustment:&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;net_5d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mean_5d_return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt;   &lt;span class="c1"&gt;# 0.20% round-trip DERIVED cost
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No future data leaks: features are built strictly chronologically; forward returns only look &lt;em&gt;ahead&lt;/em&gt; of each past state, never of "today."&lt;/p&gt;

&lt;h3&gt;
  
  
  How to reproduce the dataset in one afternoon
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;nse-bse-mcp&lt;/code&gt; (handles NSE cookie negotiation that raw &lt;code&gt;curl&lt;/code&gt; 403s on).&lt;/li&gt;
&lt;li&gt;Run the EOD bhavcopy backfill for NIFTY across your date range — this produced my 284,937 rows.&lt;/li&gt;
&lt;li&gt;Build one &lt;code&gt;features_5m&lt;/code&gt; row per day: PCR = PE_OI / CE_OI, max_pain = strike with max total OI, straddle = ATM CE+PE LTP.&lt;/li&gt;
&lt;li&gt;Apply &lt;code&gt;fit_normalizers&lt;/code&gt; + &lt;code&gt;find_similar&lt;/code&gt; exactly as above.&lt;/li&gt;
&lt;li&gt;Compute &lt;code&gt;forward_return&lt;/code&gt; and compare to the 49% win / −0.09% mean benchmark. If your sample beats it after costs, you have a real edge — publish the methodology.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire pipeline is free. The barrier was never the tooling; it was the discipline to report the null result.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT FAILED / COUNTER-EVIDENCE
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The hypothesis was rejected.&lt;/strong&gt; Similar-state forward returns did not beat the null (50% / 0% mean). I did not "find" an edge and publish it — the data said no.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intraday granularity is missing.&lt;/strong&gt; Daily rows can't capture the 5–60 minute microstructure where option-chain signals supposedly fire. A fair criticism of this experiment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single symbol.&lt;/strong&gt; NIFTY only. BANKNIFTY, stock options, and cross-asset confirmation were out of scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No realized fills.&lt;/strong&gt; These are &lt;em&gt;expectations&lt;/em&gt; from similar history, not actual traded P&amp;amp;L. Labeled honestly as research.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  LIMITATIONS (explicit non-claims)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I do &lt;strong&gt;NOT&lt;/strong&gt; claim this engine predicts NIFTY.&lt;/li&gt;
&lt;li&gt;I do &lt;strong&gt;NOT&lt;/strong&gt; claim option-chain sentiment is useless in all regimes — only that over &lt;em&gt;this&lt;/em&gt; 120-day sample it showed no tradable next-day edge.&lt;/li&gt;
&lt;li&gt;I do &lt;strong&gt;NOT&lt;/strong&gt; claim the normalized KNN is optimal — it is a baseline fix, not a finished model.&lt;/li&gt;
&lt;li&gt;Results are &lt;strong&gt;not&lt;/strong&gt; investable advice. They are a measurement.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PRACTICAL TAKEAWAYS
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Demand the win rate.&lt;/strong&gt; Any "PCR signal" seller who can't show you a 120-day forward win rate &amp;gt; 52% after costs is selling a story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalize before you compare.&lt;/strong&gt; Raw price-level similarity hides structure. This bug alone explains why many retail "scanners" only fire on same-level days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NO_TRADE is a valid output.&lt;/strong&gt; An engine that always outputs BUY/SELL is the one to fear. Mine said NO_TRADE 9/9 times — that discipline is the point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs eat everything.&lt;/strong&gt; A −0.09% gross edge becomes −0.29% net. Retail options decay does the same to your "signals."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the dataset first.&lt;/strong&gt; Six months of free EOD bhavcopy (284k rows) took one MCP + a cron. The data is the moat, not the indicator.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: So option-chain analysis is useless?&lt;/strong&gt;&lt;br&gt;
A: For &lt;em&gt;next-day directional&lt;/em&gt; calls on NIFTY over this sample, yes — no edge survived costs. For &lt;em&gt;intraday&lt;/em&gt; or &lt;em&gt;expiry-week&lt;/em&gt; dynamics, the question is open and needs minute-level data I didn't have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Why only 120 days?&lt;/strong&gt;&lt;br&gt;
A: That's what six months of trading sessions gives you. More history (multiple regimes, a budget, BANKNIFTY) would strengthen the test. Stated as a limitation, not hidden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What would change the result?&lt;/strong&gt;&lt;br&gt;
A: (1) Intraday 5-min feature rows, (2) multi-symbol confirmation, (3) a volatility-regime filter so you only trade high-conviction similarity clusters, (4) walk-forward OOS validation instead of single-pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I run this myself?&lt;/strong&gt;&lt;br&gt;
A: Yes — the pipeline is &lt;code&gt;nse-bse-mcp&lt;/code&gt; for data → SQLite → &lt;code&gt;fit_normalizers&lt;/code&gt; + &lt;code&gt;find_similar&lt;/code&gt; + &lt;code&gt;forward_return&lt;/code&gt;. All deterministic, no future leak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is this financial advice?&lt;/strong&gt;&lt;br&gt;
A: No. Educational research by a NISM XII certified educator. Consult a SEBI-registered advisor before trading.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I ran &lt;strong&gt;284,937 NIFTY option-chain rows / 120 days&lt;/strong&gt; through a similarity engine. After fixing a scaling bug (39→50 matches), the best forward edge was &lt;strong&gt;−0.09% / 49% win&lt;/strong&gt; — a coin flip that loses after costs. Most retail option-chain "signals" fail this same measurement. The real edge is the &lt;em&gt;dataset and discipline&lt;/em&gt;, not the sentiment line.&lt;/p&gt;

&lt;h2&gt;
  
  
  SOURCES
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NSE EOD bhavcopy, ingested via &lt;code&gt;nse-bse-mcp&lt;/code&gt; (free, 403-bypass), Feb 20 – Aug 18 2026. OBSERVED in &lt;code&gt;nse_research.db&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;indian-option-mcp&lt;/code&gt; live cross-check: NIFTY max-pain ₹24150 = 2646 Cr total pain (OBSERVED, live call).&lt;/li&gt;
&lt;li&gt;Methodology: similar-history KNN + walk-forward forward returns, deterministic, no future leak.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AUTHOR / CANONICAL ATTRIBUTION
&lt;/h2&gt;

&lt;p&gt;Shakti Tiwari — Nifty Option Trader, XGBoost Expert. NISM XII certified educator (not SEBI-registered advisory). Founder, OptionTradingWithAI.in. Original experiment and dataset; do not republish without attribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources &amp;amp; Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;About the author: &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;https://about.me/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OptionTradingWithAI.in (canonical home): &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;https://optiontradingwithai.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WhatsApp for research questions: &lt;a href="https://wa.me/919169650895" rel="noopener noreferrer"&gt;https://wa.me/919169650895&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to profile (real handle): &lt;a href="https://dev.to/shaktitiwari"&gt;https://dev.to/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Book: Option Trading with AI (B0H9ZNTBPK) | The AI Opportunity (B0HBBFKDQF)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nifty</category>
      <category>options</category>
      <category>pcr</category>
      <category>trading</category>
    </item>
    <item>
      <title>nse-bse-mcp Install Guide: NIFTY Option Chain Python mei Live</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:44:48 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/nse-bse-mcp-install-guide-nifty-option-chain-python-mei-live-69a</link>
      <guid>https://dev.to/shaktitiwari/nse-bse-mcp-install-guide-nifty-option-chain-python-mei-live-69a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Step-by-step of getting live NSE option-chain data via MCP — the exact setup I used for the NSE research skill. No API key, no auth circus. Agar tum NIFTY/BANKNIFTY data chahte ho bina paid vendor ke, ye read karo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What nse-bse-mcp gives
&lt;/h2&gt;

&lt;p&gt;A local MCP server (HTTP :3000) exposing 57 NSE tools: &lt;code&gt;nse_fno_historical&lt;/code&gt;, &lt;code&gt;nse_option_chain&lt;/code&gt;, &lt;code&gt;nse_filtered_option_chain&lt;/code&gt;, &lt;code&gt;nse_compile_option_chain&lt;/code&gt;, &lt;code&gt;nse_vix_historical&lt;/code&gt;, &lt;code&gt;nse_download_fno_bhavcopy&lt;/code&gt;, &lt;code&gt;nse_get_market_status&lt;/code&gt;. Ye sab NSE ke public endpoints wrap karte hain. No API key required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install (verified on macOS, node 22)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; nse-bse-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server &lt;code&gt;http://localhost:3000/mcp&lt;/code&gt; pe start hota hai. Background mei run karo taaki terminal band hone par na mare:&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="nb"&gt;nohup &lt;/span&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; nse-bse-mcp &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/nse-mcp.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Health check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/list"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;57 tools list aayenge. Agar empty aaye toh server nahi chala — log check karo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetch live chain (Python)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&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;jsonrpc&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;2.0&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;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;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&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;tools/call&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;params&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;nse_filtered_option_chain&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;arguments&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;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strike_range&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/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;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;headers&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;Content-Type&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;application/json&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;Accept&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;application/json, text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&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="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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="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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfind&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="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="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&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="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;strike&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{});&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{})&lt;/span&gt;
        &lt;span class="n"&gt;rows&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="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strikePrice&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;CE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openInterest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                     &lt;span class="n"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impliedVolatility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;rows&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="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strikePrice&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;PE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openInterest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                     &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impliedVolatility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastPrice&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;rows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real result (19-Aug-2026 09:31 IST)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NIFTY 50 = 24086.25 (Open, -0.28%)
42 rows (21 strikes × CE+PE)
Top OI strike: 24500 CE
Sample CE OI: 3761 | CE IV: 12.36% | CE last: 142.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ye numbers NSE live feed se aaye hain. Mock nahi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas (real bugs I hit)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;BANKNIFTY &lt;code&gt;range:10&lt;/code&gt; error&lt;/strong&gt; — "Response is too large" deta tha kyunki BANKNIFTY mei zyada strikes hain. Fix: &lt;code&gt;strike_range&lt;/code&gt; + &lt;code&gt;max_items&lt;/code&gt; use karo (NIFTY mei &lt;code&gt;range:10&lt;/code&gt; chal gaya, BANKNIFTY mei nahi).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BANKNIFTY response &lt;code&gt;_metadata&lt;/code&gt; wrapper mei&lt;/strong&gt; — NIFTY clean JSON deta hai, BANKNIFTY metadata mei. Parser dono handle kare (upar wala code karta hai).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry format&lt;/strong&gt; — &lt;code&gt;"25-Aug-2026"&lt;/code&gt; JSON date nahi, parse fail. Normalize &lt;code&gt;2026-08-25&lt;/code&gt; karo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CE/PE nested&lt;/strong&gt; — structure &lt;code&gt;{data:[{strikePrice, CE:{...}, PE:{...}}]}&lt;/code&gt;. Direct &lt;code&gt;json.loads(text)&lt;/code&gt; fail hoga, iterate karo.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Store to DuckDB
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;duckdb&lt;/span&gt;
&lt;span class="n"&gt;con&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;duckdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options.duckdb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;con&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE IF NOT EXISTS market_raw (
    session_date DATE, symbol VARCHAR, strike DOUBLE, type VARCHAR,
    open_interest BIGINT, iv DOUBLE, last_price DOUBLE, spot DOUBLE)&lt;/span&gt;&lt;span class="sh"&gt;"""&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="nf"&gt;fetch_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;spot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;24086.25&lt;/span&gt;
&lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executemany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT VALUES (?,?,?,?,?,?,?,?)&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;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY&lt;/span&gt;&lt;span class="sh"&gt;"&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="mi"&gt;0&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="mi"&gt;1&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="mi"&gt;2&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="mi"&gt;3&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="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;spot&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comparison: free vs paid
&lt;/h2&gt;

&lt;p&gt;| Source | Cost | Latency | Setup |&lt;br&gt;
|--|||-|&lt;br&gt;
| nse-bse-mcp | ₹0 | ~1-2s | npx command |&lt;br&gt;
| NSE official API | ₹0 but unstable | varies | auth circus |&lt;br&gt;
| Paid (TrueData etc) | ₹1500+/mo | &amp;lt;1s | subscription |&lt;br&gt;
| Manual screenshot | ₹0 | manual | time sink |&lt;/p&gt;

&lt;p&gt;Free MCP enough hai research/paper-mode ke liye.&lt;/p&gt;
&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: API key chahiye?&lt;/strong&gt; Nahi. NSE public data hai.&lt;br&gt;
&lt;strong&gt;Q: Windows pe chalega?&lt;/strong&gt; Haan, npx cross-platform hai.&lt;br&gt;
&lt;strong&gt;Q: Kitni baar call kar sakte hain?&lt;/strong&gt; NSE rate-limit hai, 5-min interval safe hai.&lt;br&gt;
&lt;strong&gt;Q: BANKNIFTY bhi?&lt;/strong&gt; Haan, &lt;code&gt;strike_range:5, max_items:50&lt;/code&gt; se 102 rows aate hain.&lt;/p&gt;
&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Background na chalaana&lt;/strong&gt; — terminal band, server mara, "MCP down" error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BANKNIFTY = NIFTY treat karna&lt;/strong&gt; — alag response format hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry format assume ISO&lt;/strong&gt; — crash karega.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Har second call&lt;/strong&gt; — rate-limit ban.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Data pipeline banane mei 80% time debugging format issues mei gaya, na ki logic mei. NSE ka response consistent nahi hai across symbols — defensive parser likhna padta hai.&lt;/p&gt;
&lt;h2&gt;
  
  
  My workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;09:30 launch recorder (5-min cron)&lt;/li&gt;
&lt;li&gt;Har 5 min: NIFTY 42 + BANKNIFTY 102 rows append&lt;/li&gt;
&lt;li&gt;16:00 EOD snapshot&lt;/li&gt;
&lt;li&gt;Sunday: features rebuild&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Data via NSE public site. Research only, not live-trading.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Deep dive: response parsing why it's tricky
&lt;/h2&gt;

&lt;p&gt;NSE MCP ek consistent API nahi deta. NIFTY ke liye &lt;code&gt;nse_filtered_option_chain&lt;/code&gt; direct JSON deta hai jisme &lt;code&gt;data&lt;/code&gt; array hai. BANKNIFTY ke liye same call &lt;code&gt;_metadata&lt;/code&gt; wrapper deta hai jisme actual data &lt;code&gt;_metadata.data&lt;/code&gt; mei hai, aur upar ek message hota hai "Response is too large — use filters." Matlb BANKNIFTY mei zyada strikes hain, isliye server ne truncate kar diya aur metadata mei wrap kar diya.&lt;/p&gt;

&lt;p&gt;Isliye mera parser dono check karta hai:&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;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agar &lt;code&gt;data&lt;/code&gt; nahi mila toh &lt;code&gt;_metadata.data&lt;/code&gt; try karta hai. Fir CE/PE nested objects iterate karke rows banata hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expiry handling
&lt;/h2&gt;

&lt;p&gt;Option chain mei multiple expiries hoti hain. MCP default nearest expiry deta hai. Agar specific expiry chahiye:&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;arguments&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;symbol&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;NIFTY&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;expiryDate&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;2026-08-25&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;strike_range&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dhyan rahe: format &lt;code&gt;25-Aug-2026&lt;/code&gt; FAIL karega (string date JSON nahi). Use &lt;code&gt;2026-08-25&lt;/code&gt; (ISO). Maine normalize function banaya hai jo &lt;code&gt;DD-Mon-YYYY&lt;/code&gt; ko &lt;code&gt;YYYY-MM-DD&lt;/code&gt; mei convert karta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling in production
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&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="nf"&gt;fetch_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URLError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MCP down, retrying in 60s&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ya launch script se restart karo
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mera &lt;code&gt;launch_recorder.py&lt;/code&gt; har run pe check karta hai ki server up hai; nahi toh &lt;code&gt;npx -y nse-bse-mcp&lt;/code&gt; start kar deta hai. Isse Cron job 24x7 chalta hai bina manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits aur etiquette
&lt;/h2&gt;

&lt;p&gt;NSE public endpoints rate-limit karte hain. 5-minute interval pe 1 call safe hai. Agar &lt;code&gt;429&lt;/code&gt; ya empty response aaye, backoff karo (exponential: 60s, 120s, 240s). Mass scraping mat karo — IP ban ho sakta hai.&lt;/p&gt;

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

&lt;p&gt;Server &lt;code&gt;localhost&lt;/code&gt; pe hi rakho. &lt;code&gt;0.0.0.0&lt;/code&gt; pe expose mat karo — koi bhi network se tumhara NSE data pull kar sakta hai. Firewall default block hai, usko mat kholo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real use case: my NSE research
&lt;/h2&gt;

&lt;p&gt;Is setup se main daily 42 (NIFTY) + 102 (BANKNIFTY) rows collect karta hoon DuckDB mei. 4 weeks mei 20+ sessions build hote hain, tab signal gate real probability nikalta hai. Bina is data ke koi model blind hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Data via NSE public site. Research only, not live-trading.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting checklist
&lt;/h2&gt;

&lt;p&gt;| Symptom | Cause | Fix |&lt;br&gt;
||-|--|&lt;br&gt;
| Empty tools/list | Server nahi chala | &lt;code&gt;nohup npx -y nse-bse-mcp &amp;amp;&lt;/code&gt; |&lt;br&gt;
| "too large" on BANKNIFTY | Range wide | &lt;code&gt;strike_range:5, max_items:50&lt;/code&gt; |&lt;br&gt;
| &lt;code&gt;_metadata&lt;/code&gt; in response | BANKNIFTY truncate | Parser handles it |&lt;br&gt;
| Expiry parse error | &lt;code&gt;25-Aug-2026&lt;/code&gt; format | Use &lt;code&gt;2026-08-25&lt;/code&gt; |&lt;br&gt;
| 429 empty | Rate limit | Backoff 60s+ |&lt;br&gt;
| CE/PE missing | Nested structure | Iterate CE{} PE{} |&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison with Indian-Option-MCP
&lt;/h2&gt;

&lt;p&gt;Doosra option: &lt;code&gt;npx -y indian-option-mcp&lt;/code&gt; (stdio mode). Difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;nse-bse-mcp&lt;/strong&gt;: HTTP, 57 tools, option chain + futures + VIX + bhavcopy. Easier to call via curl/Python.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;indian-option-mcp&lt;/strong&gt;: stdio, NSE cookies, simpler but per-call spawn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Main dono use karta hoon — nse-bse-mcp primary (HTTP stable), indian-option-mcp backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this unlocks
&lt;/h2&gt;

&lt;p&gt;Live NSE data milne se:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Real OI/IV features bante hain (no fake data).&lt;/li&gt;
&lt;li&gt;Max pain, PCR, IV skew calculate kar sakte ho.&lt;/li&gt;
&lt;li&gt;Signal gate ko real feed milta hai.&lt;/li&gt;
&lt;li&gt;Backtest realistic ban jata hai.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bina data ke ML sirf theory hai. Ye setup theory ko practice banata hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Data via NSE public site. Research only, not live-trading.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My honest setup notes
&lt;/h2&gt;

&lt;p&gt;Maine ye setup 19-Aug-2026 ko banaya tha. Pehle 2 ghante sirf format bugs fix karne mei gaye — NIFTY clean tha, BANKNIFTY ne sab bigada. Lesson: &lt;strong&gt;hamesha dono symbols test karo&lt;/strong&gt; ek hi function mei, mat assume karo ki ek kaam karega toh doosra bhi karega.&lt;/p&gt;

&lt;p&gt;Ab ye Cron job pe hai: &lt;code&gt;*/5 9-15 * * 1-5&lt;/code&gt; — market hours mei har 5 minute. Monday se Friday. Saturday/Sunday band (NSE closed). EOD snapshot 16:00 pe alag job karta hai.&lt;/p&gt;

&lt;p&gt;Data DuckDB mei accumulate ho raha hai. Abhi 1 session tha, abhi NO_TRADE gate sahi reject kar raha hai. 4 weeks baad 20+ sessions honge, tab walk-forward validator pehli real signal degi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final recommendation
&lt;/h2&gt;

&lt;p&gt;Agar tum NIFTY/BANKNIFTY par research kar rahe ho aur paid data afford nahi kar sakte, nse-bse-mcp best free option hai. 30 minute mei setup ho jata hai agar upar ke gotchas yaad rakho. Paid vendors tab consider karo jab live sub-second execution chahiye.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Data via NSE public site. Research only, not live-trading. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start (copy-paste)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Start server&lt;/span&gt;
&lt;span class="nb"&gt;nohup &lt;/span&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; nse-bse-mcp &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/nse-mcp.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;span class="c"&gt;# 2. Verify&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/mcp &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/list"}'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;span class="c"&gt;# 3. Run fetch (use code above)&lt;/span&gt;
python3 fetch_nifty.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bas. 10 minute mei live NSE data tumhare paas hai. Baaki articles mei hum isi data pe features aur signals banayenge.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Data via NSE public site. Research only, not live-trading.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;Sab kuch research purpose ke liye hai. Live trading ke liye SEBI registration aur risk management alag se chahiye. NSE data public source se aata hai, accuracy guaranteed nahi.&lt;/p&gt;

&lt;p&gt;Agar setup mei atak gaye, comment karo — main exact error dekh kar fix bata dunga.&lt;/p&gt;

&lt;h2&gt;
  
  
  More From Shakti Tiwari
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Websites:&lt;/strong&gt; &lt;a href="https://shaktitiwari.github.io/shakti-tiwari-nse" rel="noopener noreferrer"&gt;shaktitiwari.github.io/shakti-tiwari-nse&lt;/a&gt; · &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;OptionTradingWithAI.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Books:&lt;/strong&gt; &lt;em&gt;Build Your Own AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0HBBFKDQF" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;) · &lt;em&gt;Option Trading with AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0H9ZNTBPK" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Community:&lt;/strong&gt; &lt;a href="https://discord.gg/shaktitiwari" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; · &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;about.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/shaktitiwari" rel="noopener noreferrer"&gt;GitHub/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏛️ &lt;strong&gt;Entity:&lt;/strong&gt; &lt;a href="https://www.wikidata.org/wiki/Q140689249" rel="noopener noreferrer"&gt;Wikidata Q140689249&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Hermes Agent se NSE Options MCP Research kaise chalayein (Live Demo)</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:44:30 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/hermes-agent-se-nse-options-mcp-research-kaise-chalayein-live-demo-3dl1</link>
      <guid>https://dev.to/shaktitiwari/hermes-agent-se-nse-options-mcp-research-kaise-chalayein-live-demo-3dl1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;First-hand walkthrough of the &lt;code&gt;nse-options-mcp-research&lt;/code&gt; skill I built and run daily. This is not theory — it is the actual pipeline pulling live NSE option-chain data through MCP into DuckDB, with a NO_TRADE signal gate. If you want to build your own NSE research stack without paying for a data vendor, this is the blueprint.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  MCP kya hai (aur kyun?)
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol (MCP) ek open standard hai jo AI agent ko external tools, APIs aur data sources se securely connect karta hai. Traditional approach mei har API ke liye alag alag wrapper code likhna padta tha. MCP ne usko standardize kar diya: ek server banao (jo NSE data deta hai), aur agent usse tool ki tarah call karta hai.&lt;/p&gt;

&lt;p&gt;Trading context mei iska matlab: ek MCP server NSE ka live option chain, futures data, VIX historical, aur bhavcopy deta hai. Agent (Hermes) usko padh kar analysis karta hai — bina manual API auth, bina rate-limit drama.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jo maine banaya: architecture
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nse-options-mcp-research&lt;/code&gt; skill 3 hisso mei divided hai:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;snapshot_recorder.py&lt;/strong&gt; — NSE option chain fetch karta hai MCP se, 42 rows (21 strikes × CE+PE) extract karta hai, aur DuckDB mei store karta hai. Har 5 minute market hours mei chalta hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;signal_gate.py&lt;/strong&gt; — decide karta hai ki data sufficient hai ya nahi (NO_TRADE / INSUFFICIENT_HISTORY / HOLIDAY etc.). Ye gate hai, model nahi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;launch_recorder.py&lt;/strong&gt; — 5-minute cron se chalta hai. Pehle check karta hai ki MCP server up hai, fir recorder run karta hai.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Iske alawa ek &lt;code&gt;mcp.json&lt;/code&gt; config file hai jo batati hai kaunsa MCP server kahan hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live demo (real run, 19-Aug-2026)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 snapshot_recorder.py &lt;span class="nt"&gt;--symbol&lt;/span&gt; NIFTY &lt;span class="nt"&gt;--db&lt;/span&gt; options.duckdb
&lt;span class="go"&gt;NIFTY spot 24086.25 | 42 rows | top OI strike 24500 CE
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 signal_gate.py
&lt;span class="go"&gt;VERDICT: NO_TRADE | reason: INSUFFICIENT_HISTORY (need ≥20 sessions)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ye real output hai. NSE MCP ne &lt;strong&gt;NIFTY 50 = 24086.25&lt;/strong&gt; diya (live, 19-Aug-2026 09:31 IST, Open, -0.28%). Signal gate ne sahi reject kiya kyunki abhi sirf 1 session ka data hai — guide ke mutabik ≥20 sessions chahiye signals ke liye.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install (verified on macOS, node 22)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; nse-bse-mcp          &lt;span class="c"&gt;# HTTP :3000, 57 tools&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; indian-option-mcp    &lt;span class="c"&gt;# stdio, NSE cookies&lt;/span&gt;
uvx nsekit-mcp@latest       &lt;span class="c"&gt;# fallback if npm name 404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config file &lt;code&gt;~/.hermes/mcp.json&lt;/code&gt; mei daalna hai. Pehla server &lt;code&gt;http://localhost:3000/mcp&lt;/code&gt; pe chalta hai. Health check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/list"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;57 tools list aayenge — nse_fno_historical, nse_option_chain, nse_vix_historical, nse_filtered_option_chain, nse_compile_option_chain, nse_download_fno_bhavcopy, nse_get_market_status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python se fetch ka actual code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIFTY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&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;jsonrpc&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;2.0&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;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;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&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;tools/call&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;params&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;nse_filtered_option_chain&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;arguments&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;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strike_range&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/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;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;headers&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;Content-Type&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;application/json&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;Accept&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;application/json, text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&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="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# MCP kabhi clean JSON, kabhi SSE "data: {...}" deta hai
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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="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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# BANKNIFTY mei _metadata wrapper mei aata hai
&lt;/span&gt;    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfind&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="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="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&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="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;strike&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{});&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,{})&lt;/span&gt;
        &lt;span class="n"&gt;rows&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="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strikePrice&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;CE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openInterest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                     &lt;span class="n"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impliedVolatility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;rows&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="n"&gt;strike&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strikePrice&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;PE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openInterest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                     &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impliedVolatility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastPrice&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;rows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ye exact code maine use kiya. 42 rows (21 strikes × CE+PE) aate hain, real OI 3761, IV 12.36, spot 24092.6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kyun NO_TRADE pehle?
&lt;/h2&gt;

&lt;p&gt;Retail traders galat confidence pe trade karte hain. 55% confidence pe "BUY" bol dete hain. V1 paper trading mei result tha: &lt;strong&gt;31.6% win rate, −₹90.3k PnL&lt;/strong&gt;. The fix isn't a better model — it's a &lt;strong&gt;gate&lt;/strong&gt; that refuses to vote when evidence is thin.&lt;/p&gt;

&lt;p&gt;Signal gate ka rule: &lt;strong&gt;data kam hai toh mat batao direction&lt;/strong&gt;. Jab ≥20 sessions accumulate honge (5-min recorder se ~4 weeks market days), tab real probability niklegi. Tab tak system sirf data collect karta hai. Ye design hai, bug nahi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas (real bugs I hit)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;BANKNIFTY &lt;code&gt;range:10&lt;/code&gt; error&lt;/strong&gt; — "Response is too large" deta tha. Fix: &lt;code&gt;strike_range&lt;/code&gt; + &lt;code&gt;max_items&lt;/code&gt; use karo (NIFTY mei &lt;code&gt;range:10&lt;/code&gt; chal gaya, BANKNIFTY mei nahi).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP response format inconsistent&lt;/strong&gt; — NIFTY clean JSON deta hai, BANKNIFTY &lt;code&gt;_metadata&lt;/code&gt; wrapper mei. Parser dono handle kare.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry format&lt;/strong&gt; — &lt;code&gt;"25-Aug-2026"&lt;/code&gt; JSON date nahi hai, parse fail hota tha. Normalize &lt;code&gt;2026-08-25&lt;/code&gt; karna pada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CE/PE nested objects&lt;/strong&gt; — pehle &lt;code&gt;json.loads(text)&lt;/code&gt; direct fail ho raha tha kyunki structure &lt;code&gt;{data:[{strikePrice, CE:{...}, PE:{...}}]}&lt;/code&gt; tha. Iterate karna pada.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Signal gate ka actual logic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MIN_SESSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT count(DISTINCT session_date) FROM market_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_SESSIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&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;NO_TRADE&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;INSUFFICIENT_HISTORY&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MIN_SESSIONS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# + live source check, holiday check, stale-feed check
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other gates: live source down → NO_TRADE; NSE holiday/weekend → NO_TRADE; last snapshot &amp;gt;10 min old → NO_TRADE; hard failure → kill-switch trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data schema (DuckDB)
&lt;/h2&gt;

&lt;p&gt;4 tables: &lt;code&gt;market_raw&lt;/code&gt; (raw snapshot), &lt;code&gt;features_5m&lt;/code&gt; (PCR, IV skew, max pain), &lt;code&gt;signals&lt;/code&gt; (gate output), &lt;code&gt;outcomes&lt;/code&gt; (future result for replay). Har row mei &lt;code&gt;truth_weight&lt;/code&gt; hota hai (PAIR_FULL=1.0 se stale=0.0) — stale rows training se exclude.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest status
&lt;/h2&gt;

&lt;p&gt;Pipeline &lt;strong&gt;research/paper mode&lt;/strong&gt; mei hai. Live trades nahi karta, koi broker connected nahi. Signals abhi NO_TRADE de rahe hain by design — yahi sahi hai. Jab 20+ sessions build honge, walk-forward validator check karega.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Kya ye paid data ke bina chalta hai?&lt;/strong&gt; Haan, NSE public endpoints wrap hain, API key nahi lagta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Kitna compute chahiye?&lt;/strong&gt; Minimal — ek Mac pe npx server + Python script. 5-min interval pe 42 rows insert, kuch MB per day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Kya main isse apni strategy run kar sakta hoon?&lt;/strong&gt; Haan, par apna risk management khud likho. Ye research scaffold hai.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: BANKNIFTY bhi support hai?&lt;/strong&gt; Haan, &lt;code&gt;strike_range&lt;/code&gt; + &lt;code&gt;max_items&lt;/code&gt; fix ke baad 102 rows aate hain (spot 57190.4).&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;5-min recorder 4 weeks chalaao → 20+ sessions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features_5m&lt;/code&gt; populate karo (PCR, max pain, IV skew).&lt;/li&gt;
&lt;li&gt;Walk-forward validator se signal quality check.&lt;/li&gt;
&lt;li&gt;Sirf tab broker layer consider karo (paper mode pehle).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real NSE data walkthrough (actual numbers)
&lt;/h2&gt;

&lt;p&gt;Jab maine 19-Aug-2026 09:31 IST pe snapshot liya, ye thi structure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NIFTY spot&lt;/td&gt;
&lt;td&gt;24086.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strikes captured&lt;/td&gt;
&lt;td&gt;21 (CE + PE each)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total rows&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top OI strike&lt;/td&gt;
&lt;td&gt;24500 CE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sample CE OI&lt;/td&gt;
&lt;td&gt;3761&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sample CE IV&lt;/td&gt;
&lt;td&gt;12.36%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sample CE last price&lt;/td&gt;
&lt;td&gt;142.50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ye numbers NSE ke live public feed se aaye hain, koi mock nahi. DuckDB mei &lt;code&gt;market_raw&lt;/code&gt; table mei &lt;code&gt;session_date&lt;/code&gt;, &lt;code&gt;symbol&lt;/code&gt;, &lt;code&gt;strike&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;open_interest&lt;/code&gt;, &lt;code&gt;iv&lt;/code&gt;, &lt;code&gt;last_price&lt;/code&gt;, &lt;code&gt;spot&lt;/code&gt; columns store hote hain. Har 5 min ek naya snapshot append hota hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paid vendors se comparison
&lt;/h2&gt;

&lt;p&gt;| Source | Cost | Latency | Effort |&lt;br&gt;
|--|||--|&lt;br&gt;
| nse-bse-mcp (free) | ₹0 | ~1-2s | npx command |&lt;br&gt;
| Traditional NSE API | ₹0 but unstable | varies | auth circus |&lt;br&gt;
| Paid vendor (TrueData etc) | ₹1500+/mo | &amp;lt;1s | subscription |&lt;br&gt;
| Manual screenshot | ₹0 | manual | time sink |&lt;/p&gt;

&lt;p&gt;Free MCP enough hai agar aap research/paper-mode mei ho. Paid lena tab jab live execution chahiye sub-second latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes (maine kiye)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;MCP server background mei nahi chala&lt;/strong&gt; — terminal band hone par server mar gaya, recorder "MCP down" bolta tha. Fix: launch script check karta hai aur start karta hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BANKNIFTY ko NIFTY jaisa treat kiya&lt;/strong&gt; — &lt;code&gt;range:10&lt;/code&gt; dono pe kaam karega assume kiya, BANKNIFTY mei fail hua. Har symbol ka response test karo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry format assume kiya ISO&lt;/strong&gt; — &lt;code&gt;"25-Aug-2026"&lt;/code&gt; pass kar diya, parser crash. Ab normalize karke &lt;code&gt;2026-08-25&lt;/code&gt; bhejta hoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal gate ko ignore kiya&lt;/strong&gt; — pehle bina gate ke model run kiya, false signals aaye. Gate compulsory hai.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Mera actual weekly workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monday 09:30&lt;/strong&gt; — launch_recorder start (cron auto karta hai ab).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Har 5 min&lt;/strong&gt; — 42 rows NIFTY + 102 rows BANKNIFTY append.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;16:00&lt;/strong&gt; — EOD snapshot, session close mark.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunday&lt;/strong&gt; — &lt;code&gt;features_5m&lt;/code&gt; rebuild, session count check (target 20+).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jab 20+ sessions&lt;/strong&gt; — walk-forward validator pehli baar real signal nikalega.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security note
&lt;/h2&gt;

&lt;p&gt;MCP server &lt;code&gt;localhost&lt;/code&gt; pe hi chalta hai, internet pe expose mat karo. NSE data public hai par apne API keys (DEV.to, GitHub) &lt;code&gt;.env&lt;/code&gt; mei rakho, hardcode mat karo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn building this yourself
&lt;/h2&gt;

&lt;p&gt;Is project ko banane mei mujhe 3 cheezein clearly samajh aayi:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data is the product.&lt;/strong&gt; Model se pehle data pipeline solid hona chahiye. Meri V1 mei partition bug ne poora feature zero kar diya — model kuch seekh hi nahi paya. Ab &lt;code&gt;null_count&lt;/code&gt; telemetry har batch mei check karta hoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate &amp;gt; Model.&lt;/strong&gt; Best model bhi garbage signal degi agar context weak hai. NO_TRADE bolna seekhna zaroori hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honesty scales.&lt;/strong&gt; Jab maine PF 0.53 (loss) publish kiya instead of hiding, readers ne trust kiya. Fake "90% accuracy" se better hai real "60% directional, but unprofitable exits."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agar aap apna version banana chahte ho, start with &lt;code&gt;nse-bse-mcp&lt;/code&gt; install + ek simple DuckDB table. Signal gate last mei add karna, pehle data accumulate karo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Ye thi complete walkthrough of &lt;code&gt;nse-options-mcp-research&lt;/code&gt; — from install to live data to NO_TRADE gate. Sari code real hai, sari numbers NSE live feed se hain. Aage ke articles mei hum isi data pe features (PCR, max pain) aur walk-forward validation cover karenge.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More From Shakti Tiwari
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Websites:&lt;/strong&gt; &lt;a href="https://shaktitiwari.github.io/shakti-tiwari-nse" rel="noopener noreferrer"&gt;shaktitiwari.github.io/shakti-tiwari-nse&lt;/a&gt; · &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;OptionTradingWithAI.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Books:&lt;/strong&gt; &lt;em&gt;Build Your Own AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0HBBFKDQF" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;) · &lt;em&gt;Option Trading with AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0H9ZNTBPK" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Community:&lt;/strong&gt; &lt;a href="https://discord.gg/shaktitiwari" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; · &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;about.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/shaktitiwari" rel="noopener noreferrer"&gt;GitHub/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏛️ &lt;strong&gt;Entity:&lt;/strong&gt; &lt;a href="https://www.wikidata.org/wiki/Q140689249" rel="noopener noreferrer"&gt;Wikidata Q140689249&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Signal Gate Design: NO_TRADE kab bolna chahiye ( 20 sessions rule)</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:43:01 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/signal-gate-design-notrade-kab-bolna-chahiye-20-sessions-rule-42gc</link>
      <guid>https://dev.to/shaktitiwari/signal-gate-design-notrade-kab-bolna-chahiye-20-sessions-rule-42gc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;From &lt;code&gt;nse-options-mcp-research/signal_gate.py&lt;/code&gt; — why a trading system should say "I don't know" most of the time, and the exact rule I coded. If your system always says BUY/SELL, ye padho.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The trap
&lt;/h2&gt;

&lt;p&gt;Retail systems har bar signal dete hain. Confidence 55% pe bhi "BUY" bol dete hain. Result: V1 paper trading mei &lt;strong&gt;31.6% win, −₹90.3k PnL&lt;/strong&gt;. The fix isn't a better model — it's a &lt;strong&gt;gate&lt;/strong&gt; that refuses to vote when evidence is thin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a signal gate does
&lt;/h2&gt;

&lt;p&gt;Input: market snapshot + session history. Output: ek decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TRADE&lt;/code&gt; — sufficient evidence, session/hours/holiday rules pass&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NO_TRADE&lt;/code&gt; — reason ke saath: &lt;code&gt;INSUFFICIENT_HISTORY&lt;/code&gt;, &lt;code&gt;NO_LIVE_SOURCE&lt;/code&gt;, &lt;code&gt;STALE_FEED&lt;/code&gt;, &lt;code&gt;HOLIDAY&lt;/code&gt;, &lt;code&gt;LOW_CONFIDENCE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gate model se pehle aata hai. Model kuch bhi bole, gate veto kar sakta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ≥20 sessions rule
&lt;/h2&gt;

&lt;p&gt;Guide ka hard rule: &lt;strong&gt;no trade signal until ≥20 recorded sessions&lt;/strong&gt; in DuckDB history. Why? 1-2 sessions ka "pattern" noise hai. 20+ sessions se basic regime/volatility stats bante hain.&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;MIN_SESSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT count(DISTINCT session_date) FROM market_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_SESSIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&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;NO_TRADE&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;INSUFFICIENT_HISTORY&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MIN_SESSIONS&lt;/span&gt;&lt;span class="si"&gt;}&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;h2&gt;
  
  
  Other gates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live source check&lt;/strong&gt; — MCP down hai toh NO_TRADE (stale data pe trade nahi).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Holiday/weekend&lt;/strong&gt; — NSE closed → NO_TRADE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale feed&lt;/strong&gt; — last snapshot &amp;gt; 10 min old → NO_TRADE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kill-switch&lt;/strong&gt; — any hard failure → trip, block all trades.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Now() IST handling
&lt;/h2&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;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&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;timedelta&lt;/span&gt;
&lt;span class="n"&gt;now_ist&lt;/span&gt; &lt;span class="o"&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="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DB naive IST store karta hai, isliye naive compare. Pehle aware/naive mismatch se tz error aata tha — fix kiya.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real result
&lt;/h2&gt;

&lt;p&gt;Mera system abhi &lt;strong&gt;NO_TRADE / INSUFFICIENT_HISTORY&lt;/strong&gt; deta hai (sirf 1 session). Ye sahi hai. 5-min recorder 4 weeks mei 20+ sessions build karega, tab real probability niklegi.&lt;/p&gt;

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

&lt;p&gt;Ek system jo NO_TRADE 90% bolta hai aur sahi 10% mei trades kare = survivable. Ek system jo har bar trade kare = margin call. Gate pehle, model baad mei.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without gate&lt;/th&gt;
&lt;th&gt;With gate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Always signals&lt;/td&gt;
&lt;td&gt;Signals only when ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55% conf trades&lt;/td&gt;
&lt;td&gt;&amp;lt;threshold = NO_TRADE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;−₹90k PnL&lt;/td&gt;
&lt;td&gt;Protected capital&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False confidence&lt;/td&gt;
&lt;td&gt;Honest uncertainty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Design principles
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Gate model se independent hona chahiye (model bias na le).&lt;/li&gt;
&lt;li&gt;Har reason logged hona chahiye (audit trail).&lt;/li&gt;
&lt;li&gt;Kill-switch hard fail pe trip kare.&lt;/li&gt;
&lt;li&gt;Session/holiday rules non-bypassable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: 20 sessions kyun?&lt;/strong&gt; Basic stats ke liye minimum sample. Kam mei noise.&lt;br&gt;
&lt;strong&gt;Q: Gate model ko override kar sakta?&lt;/strong&gt; Haan, NO_TRADE har time model veto karega.&lt;br&gt;
&lt;strong&gt;Q: Live source down toh?&lt;/strong&gt; NO_TRADE, stale pe trade nahi.&lt;br&gt;
&lt;strong&gt;Q: Kill-switch manual?&lt;/strong&gt; Auto trip on hard failure + manual bhi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Gate ko model ke baad lagana — pehle lagao.&lt;/li&gt;
&lt;li&gt;Session count ignore karna — 1 session pe trade karna.&lt;/li&gt;
&lt;li&gt;Stale feed allow karna — 10 min old data pe trade.&lt;/li&gt;
&lt;li&gt;Kill-switch na hona — hard fail pe bhi trade.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;V1 mei gate nahi tha, isliye 31.6% win pe bhi trade ho raha tha. V2 mei gate first line of defense hai. Ab system 4 weeks tak NO_TRADE bolega — wo loss nahi, protection hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: gate logic in code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&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="c1"&gt;# 1. Live source
&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;snapshot&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&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;NO_TRADE&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;NO_LIVE_SOURCE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Session count
&lt;/span&gt;    &lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT count(DISTINCT session_date) FROM market_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_SESSIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&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;NO_TRADE&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;INSUFFICIENT_HISTORY&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MIN_SESSIONS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 3. Holiday/weekend
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_nse_holiday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now_ist&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&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;NO_TRADE&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;HOLIDAY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 4. Stale feed
&lt;/span&gt;    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now_ist&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&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;NO_TRADE&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;STALE_FEED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 5. Model confidence
&lt;/span&gt;    &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&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;conf&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&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;NO_TRADE&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;LOW_CONFIDENCE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&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;TRADE&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;OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Session build timeline
&lt;/h2&gt;

&lt;p&gt;5-min recorder 2 symbols × ~124 rows = ~250 rows/session. 20 sessions ≈ 4 weeks market days. Tab tak system data collect karta hai, trade nahi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 0.60 confidence threshold
&lt;/h2&gt;

&lt;p&gt;V1 mei 55-64% confidence pe trade hue, sab loss. 0.60 conservative start hai. Jab data build hoga, calibration se threshold tune karna (isotonic).&lt;/p&gt;

&lt;h2&gt;
  
  
  Kill-switch design
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;killswitch_tripped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&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="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO scan_log VALUES (?, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;KILLSWITCH&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now_ist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&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;NO_TRADE&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;KILLSWITCH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hard failure (MCP crash, DB corrupt) pe trip. Manual reset only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world analogy
&lt;/h2&gt;

&lt;p&gt;Gate ek bouncer hai club ke bahar. Model andar dance karna chahta hai, bouncer check karta hai: ID hai? (session) Source legit? (live) Time sahi? (holiday) Bouncer na bola toh andar nahi jaane deta.&lt;/p&gt;

&lt;h2&gt;
  
  
  My experience
&lt;/h2&gt;

&lt;p&gt;Pehle bina gate ke model ko bharosa tha. 31.6% win dekh kar samjha ki problem model nahi, discipline thi. Gate ne discipline enforce kiya — ab system 4 weeks chup rahega, jo sahi hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Extended FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Gate model se independent kyun?&lt;/strong&gt; Model apna bias defend karega. Gate neutral check kare.&lt;br&gt;
&lt;strong&gt;Q: 20 sessions bad mei kya?&lt;/strong&gt; Walk-forward validator real signal nikalta hai, tab TRADE allow.&lt;br&gt;
&lt;strong&gt;Q: Confidence threshold tune kaise?&lt;/strong&gt; Calibration set pe isotonic regression, false-positives minimize.&lt;br&gt;
&lt;strong&gt;Q: Manual override?&lt;/strong&gt; Sirf emergency kill-switch, normal NO_TRADE auto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls (retail)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Always-on signals&lt;/strong&gt; — 55% pe bhi trade. Capital wipe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No session gate&lt;/strong&gt; — 1 day data pe pattern seekh ke trade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale data trade&lt;/strong&gt; — 30 min purani snapshot pe decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No kill-switch&lt;/strong&gt; — crash mei bhi orders.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Comparison: my V1 vs V2
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;V1&lt;/th&gt;
&lt;th&gt;V2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gate&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;5-layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session min&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result&lt;/td&gt;
&lt;td&gt;−₹90.3k&lt;/td&gt;
&lt;td&gt;Protected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confidence&lt;/td&gt;
&lt;td&gt;55% trades&lt;/td&gt;
&lt;td&gt;&amp;lt;60% NO_TRADE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What you should build
&lt;/h2&gt;

&lt;p&gt;Agar tum apna system bana rahe ho:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gate model se pehle lagao.&lt;/li&gt;
&lt;li&gt;Session minimum set karo (20+).&lt;/li&gt;
&lt;li&gt;Live source + stale check.&lt;/li&gt;
&lt;li&gt;Kill-switch.&lt;/li&gt;
&lt;li&gt;Har decision log karo.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Signal gate trading system ka seatbelt hai. Bina uske tum 31.6% win pe bhi trade karoge aur paise gawayoge. NO_TRADE bolna seekho — wo weakness nahi, strength hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked scenario
&lt;/h2&gt;

&lt;p&gt;Monday 09:35 IST. Snapshot aaya. Gate evaluate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Live source? YES (MCP up).&lt;/li&gt;
&lt;li&gt;Sessions? 1/20 → &lt;strong&gt;NO_TRADE / INSUFFICIENT_HISTORY&lt;/strong&gt;. Stop. Model call hi nahi hua.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Week 4, 21 sessions. Gate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Live source? YES.&lt;/li&gt;
&lt;li&gt;Sessions? 21/20 → pass.&lt;/li&gt;
&lt;li&gt;Holiday? No.&lt;/li&gt;
&lt;li&gt;Stale? 3 min old → pass.&lt;/li&gt;
&lt;li&gt;Confidence? 0.67 → &lt;strong&gt;TRADE&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ye difference hai gate wale aur bina gate wale mei.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I coded it this way
&lt;/h2&gt;

&lt;p&gt;V1 ke sabse dardnaak loss tab aaye jab 1-2 sessions ka data tha aur model confident tha. Gate ne wo band kar diya. Ab system 4 weeks discipline se data collect karega, fir trade karega.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Gate ko model se alag rakho (separate module).&lt;/li&gt;
&lt;li&gt;Har reason log karo — audit trail future debug ke liye.&lt;/li&gt;
&lt;li&gt;Threshold conservative rakho pehle (0.60), bad mei tune.&lt;/li&gt;
&lt;li&gt;Kill-switch non-bypassable ho.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The philosophy
&lt;/h2&gt;

&lt;p&gt;Trading mei "not trading" bhi ek position hai. Zyada traders isko bhool jate hain. Gate system ko force karta hai ki wo apni limits jane. 90% time NO_TRADE bolna = 90% time capital safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Signal gate = system ka seatbelt. 5 layers (live source, sessions≥20, holiday, stale, confidence≥0.60) + kill-switch. V1 bina gate ke −₹90k gaya, V2 gate ke saath protected. Build it first, model later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Action plan
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Model se pehle gate likho.&lt;/li&gt;
&lt;li&gt;Session minimum 20 rakho.&lt;/li&gt;
&lt;li&gt;Live source + stale check lagao.&lt;/li&gt;
&lt;li&gt;Kill-switch banao.&lt;/li&gt;
&lt;li&gt;Har decision log karo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bina gate ke mat trade karo — V1 ne sikha diya ye.&lt;/p&gt;

&lt;p&gt;Agar tumhare system mei bhi har bar signal aa raha hai, gate nahi hai. 5 layers lagao, dekho kitne signals survive karte hain. Shayad 90% NO_TRADE ho — wo hi sahi hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final note
&lt;/h2&gt;

&lt;p&gt;Gate design mei sabse zaroori: model ko veto power dena. Model chahe jitna confident ho, gate NO_TRADE bol sakta hai. Ye hierarchy retail systems mei missing hoti hai, aur isliye woh blow up hote hain.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm sharing this
&lt;/h2&gt;

&lt;p&gt;Zyada "AI trading" content fake 90% accuracy bechta hai. Mera system abhi NO_TRADE de raha hai by design — wo honest hai. Gate lagana hi wo cheez hai jo retail ko pro bana ti hai. Build it, respect it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next: hum isi gate + data pe walk-forward validator chalayenge jab 20 sessions build honge. Tab real signals milengi.&lt;/p&gt;

&lt;p&gt;Build the gate first. Your capital will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  More From Shakti Tiwari
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Websites:&lt;/strong&gt; &lt;a href="https://shaktitiwari.github.io/shakti-tiwari-nse" rel="noopener noreferrer"&gt;shaktitiwari.github.io/shakti-tiwari-nse&lt;/a&gt; · &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;OptionTradingWithAI.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Books:&lt;/strong&gt; &lt;em&gt;Build Your Own AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0HBBFKDQF" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;) · &lt;em&gt;Option Trading with AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0H9ZNTBPK" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Community:&lt;/strong&gt; &lt;a href="https://discord.gg/shaktitiwari" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; · &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;about.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/shaktitiwari" rel="noopener noreferrer"&gt;GitHub/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏛️ &lt;strong&gt;Entity:&lt;/strong&gt; &lt;a href="https://www.wikidata.org/wiki/Q140689249" rel="noopener noreferrer"&gt;Wikidata Q140689249&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Options-Buyer V2 Architecture: ek model kyun fail hota hai</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:42:43 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/options-buyer-v2-architecture-ek-model-kyun-fail-hota-hai-51df</link>
      <guid>https://dev.to/shaktitiwari/options-buyer-v2-architecture-ek-model-kyun-fail-hota-hai-51df</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;From the &lt;code&gt;options-buyer-system-v2&lt;/code&gt; skill — the lesson that fixed chronic CE/PE confusion in V1. If tumne bhi "ek model se sab kuch" try kiya hai aur fail hue ho, ye padho.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  V1 ki galti (root cause)
&lt;/h2&gt;

&lt;p&gt;Ek XGBoost model se ek fuzzy sawal pucha: &lt;strong&gt;"CE ya PE?"&lt;/strong&gt; — raw premium data se. Premium ek transformed signal hai (underlying move × delta × gamma × IV × theta × spread × strike distance × liquidity). Model ne noise utna hi seekha jitna signal. Direction puchne ke bajaye, humne premium ko direction padhne ki koshish ki — galat tha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence (V1 logs se)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Balanced accuracy months tak &lt;strong&gt;51-61%&lt;/strong&gt; atki (hyperparams kabhi tune nahi hue, &lt;code&gt;lr=0.02, depth=3&lt;/code&gt; defaults).&lt;/li&gt;
&lt;li&gt;Partition bug ne &lt;code&gt;iv_change_1d&lt;/code&gt; poora zero kar diya silently (single-row group shift).&lt;/li&gt;
&lt;li&gt;Rollup flag ne 15m bars 1 row/day compress kar diye — &lt;strong&gt;760× data loss&lt;/strong&gt; (387 sequences instead of 295K+).&lt;/li&gt;
&lt;li&gt;Live paper: &lt;strong&gt;31.6% win, −₹90.3k PnL&lt;/strong&gt;, confidence sirf 55-64%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  V2 principle: split the question
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;underlying mechanics  →  side, range, ETA, invalidation
option chain scanner  →  contract worth paying for?
XGBoost (many heads)  →  thin calibrated learner on clean mechanics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Underlying side decide karta hai; option contract execution eligibility.&lt;/strong&gt; CE/PE premium validate kiya jata hai, seekha nahi. V1 mei ye dono mix the.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many shallow heads, not one deep model
&lt;/h2&gt;

&lt;p&gt;Alag narrow heads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;underlying_up/down_touch_(15/30/60)m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ce_1p3x / ce_1p5x / ce_2p0x&lt;/code&gt; aur &lt;code&gt;pe_1p3x / pe_1p5x / pe_2p0x&lt;/code&gt; (alag CE/PE)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no_trade_quality&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ye ek change V1 ke CE/PE confusion ko khatam karta hai. Meta-label: "mechanics ne side pick kiya, ab kya humein ye trade lena hai?" — ye ek sawal confusion hata deta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shallow regularized grid
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lr 0.015-0.035 | n_est 800-2000 &lt;span class="o"&gt;(&lt;/span&gt;early stop&lt;span class="o"&gt;)&lt;/span&gt; | max_depth 2-3
min_child_weight 12-40 | gamma 0.1-2.0 | subsample 0.65-0.90
colsample 0.55-0.85 | reg_alpha 0.5-3.0 | reg_lambda 6-20
scale_pos_weight &lt;span class="o"&gt;=&lt;/span&gt; min&lt;span class="o"&gt;(&lt;/span&gt;neg/pos, 8.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;V1 ke intraday head mei 1280 mei sirf &lt;strong&gt;8 features&lt;/strong&gt; non-zero gain the. Shallow + regularized jawab hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Truth-quality tiering
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;truth_weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.00&lt;/span&gt;  &lt;span class="n"&gt;PAIR_FULL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt;
            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.70&lt;/span&gt;  &lt;span class="n"&gt;PAIR_PARTIAL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;
            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.40&lt;/span&gt;  &lt;span class="n"&gt;LTP_ONLY&lt;/span&gt;
            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt;  &lt;span class="n"&gt;MIXED_PAIR&lt;/span&gt;
            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.00&lt;/span&gt;  &lt;span class="n"&gt;stale&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;broken&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rows at 0.00 training se exclude, never down-weight-keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Null-safe contract
&lt;/h2&gt;

&lt;p&gt;Har feature ka explicit null policy: &lt;code&gt;forward_fill_within_session&lt;/code&gt;, &lt;code&gt;zero_is_valid_default&lt;/code&gt;, &lt;code&gt;require_and_drop_row&lt;/code&gt;, &lt;code&gt;median_impute_with_flag&lt;/code&gt;. &lt;code&gt;fillna(0.0)&lt;/code&gt; blind use BANNED (V1 ka &lt;code&gt;iv_change_1d&lt;/code&gt; bug yahin se aaya).&lt;/p&gt;

&lt;h2&gt;
  
  
  Overfit gate (hard promotion)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;overfit_gap = train_metric − test_metric&lt;/code&gt;. &lt;strong&gt;&amp;gt; 0.15 → BLOCK.&lt;/strong&gt; Har head, har retrain pe log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Label design (sharp heads)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Triple-barrier: &lt;code&gt;y_up_touch&lt;/code&gt; / &lt;code&gt;y_down_touch&lt;/code&gt; / &lt;code&gt;y_no_touch&lt;/code&gt;, k∈(0.5/0.75/1.0) ATR, horizon∈(15/30/60/120)m.&lt;/li&gt;
&lt;li&gt;Option buyer barriers: &lt;code&gt;y_1p3x, y_1p5x, y_2p0x&lt;/code&gt; + &lt;code&gt;mae_before_1p5x&lt;/code&gt; + &lt;code&gt;minutes_to_1p5x&lt;/code&gt;. Alag CE/PE.&lt;/li&gt;
&lt;li&gt;AFT time-to-hit (censored survival).&lt;/li&gt;
&lt;li&gt;Meta-label: mechanics picks side, model answers "take trade?".&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real results (honest)
&lt;/h2&gt;

&lt;p&gt;V2 abhi research stage hai. Direction skill real hai (top-decile 60.5% accuracy) par fixed-SL backtest PF 0.53 (losing). Kyun? Microstructure beats prediction — 0.6/1.8 ATR SL/TP pe price SL touch kar leta hai TP reverse hone se pehle. Lesson: directional edge alone enough nahi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison V1 vs V2
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;V1&lt;/th&gt;
&lt;th&gt;V2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;1 deep&lt;/td&gt;
&lt;td&gt;many shallow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CE/PE&lt;/td&gt;
&lt;td&gt;confused&lt;/td&gt;
&lt;td&gt;separate heads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nulls&lt;/td&gt;
&lt;td&gt;silent fill&lt;/td&gt;
&lt;td&gt;explicit policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;k-fold&lt;/td&gt;
&lt;td&gt;purged+embargo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Promotion&lt;/td&gt;
&lt;td&gt;manual&lt;/td&gt;
&lt;td&gt;automatic gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyperparams&lt;/td&gt;
&lt;td&gt;default&lt;/td&gt;
&lt;td&gt;tuned (Optuna)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Kyun alag CE/PE heads?&lt;/strong&gt; Premium transformed hai, dono side alag dynamics.&lt;br&gt;
&lt;strong&gt;Q: Shallow kyun?&lt;/strong&gt; Deep overfit karta hai 1280 features mei.&lt;br&gt;
&lt;strong&gt;Q: PF 0.53 matlab?&lt;/strong&gt; System abhi losing hai, hidden nahi.&lt;br&gt;
&lt;strong&gt;Q: Live trade?&lt;/strong&gt; Nahin, paper/research only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes (V1 se)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Fuzzy single question ("CE ya PE?").&lt;/li&gt;
&lt;li&gt;Blind fillna(0).&lt;/li&gt;
&lt;li&gt;Rollup flag ignore (760× loss).&lt;/li&gt;
&lt;li&gt;Default hyperparams.&lt;/li&gt;
&lt;li&gt;Promotion bina gap check.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;V1 ka sabse bada error: direction aur execution ek model mei mix kar diya. V2 ne split kiya — underlying side decide karta hai, contract eligibility alag. Plus null-safety aur gate ne silent bugs rok diye.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: feature families (real contract)
&lt;/h2&gt;

&lt;p&gt;12 families, 117 columns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Price Action (25): rvol, ATR(14), effort, close_loc, wick_bias, range HH/LL, accept/retest/failed-auction.&lt;/li&gt;
&lt;li&gt;Microstructure (12): close quality, inside-bar, velocity, trend, pressure.&lt;/li&gt;
&lt;li&gt;Order Flow (10): harmony, efficiency ratio, absorption up/down, aggressive/passive.&lt;/li&gt;
&lt;li&gt;Operator (4): expand/absorb signals.&lt;/li&gt;
&lt;li&gt;S/R + Breakout (6): sr_score [0-100], break_prob [5-95], bull/bear power.&lt;/li&gt;
&lt;li&gt;Time (2): hour_sin, hour_cos.&lt;/li&gt;
&lt;li&gt;Lag (6): prev_return_1/3/5, prev_vol_1/3/5.&lt;/li&gt;
&lt;li&gt;Multi-Timeframe (4): h1 close vs SMA, range ratio, vol ratio, h1 trend.&lt;/li&gt;
&lt;li&gt;Volatility Regime (3): vol_regime, contraction, expansion.&lt;/li&gt;
&lt;li&gt;Side Score (12): alignment, active side, CE/PE scores, gate states.&lt;/li&gt;
&lt;li&gt;Options (3): pc_vol_ratio, pc_oi_ratio, total_premium.&lt;/li&gt;
&lt;li&gt;Operator Anchor (12): anchor scores, accept zones, liquidity levels.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Feature importance well-distributed thi — koi single feature 25% se upar nahi. Top: &lt;code&gt;prev_return_5&lt;/code&gt; (21.3%), &lt;code&gt;range_low&lt;/code&gt; (15.1%), &lt;code&gt;atr&lt;/code&gt; (15.1%).&lt;/p&gt;

&lt;h2&gt;
  
  
  Real bug autopsy (V1)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;25-bar label shift&lt;/strong&gt;: index mismatch ne labels 25 bars shift kar diya. PnL −42% se −6.26% gaya fix ke baad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensemble meta-model mismatch&lt;/strong&gt;: dimension error ne ensemble mara (val F1=0). Disabled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Walk-forward" jo nahi tha&lt;/strong&gt;: pipeline "walk-forward" print karti thi par single fixed split use karti thi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;max_hold mismatch&lt;/strong&gt;: labels 6 bars, backtest 8 — silent inconsistency.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why shallow + regularized
&lt;/h2&gt;

&lt;p&gt;V1 ke intraday head mei Optuna &lt;code&gt;n_estimators=1080-1620&lt;/code&gt; hardcoded production mei bhej diya — live prediction 10s gateway cap se timeout. V2 mei early-stop + timeout budget check. Plus depth 2-3, reg_lambda 6-20 — 1280 mei sirf 8 features non-zero gain the, regularizer ne noise prune kiya.&lt;/p&gt;

&lt;h2&gt;
  
  
  The promotion gate in code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;overfit_gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_metric&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;test_metric&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;overfit_gap&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK promotion:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overfit_gap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="c1"&gt;# else promote to shadow (paper) for &amp;gt;=1 session
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;V1 mei ye manual tha, V2 mei automatic har head pe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest verdict
&lt;/h2&gt;

&lt;p&gt;V2 cleaner architecture hai, par abhi bhi research. Directional edge real hai (60.5% top-decile) par microstructure usse eat kar leta hai PF 0.53 pe. Next: SL 1.0 ATR widen, trailing stops, ensemble re-enable, online learning, full options surface.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My V2 build workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Label builder: triple-barrier + option-buyer barrier + AFT + meta-label, per-feature null-count report jo threshold breach pe fail karta hai.&lt;/li&gt;
&lt;li&gt;Feature builder: mechanics-first contract, sample-weighted (truth_weight × contract_weight × recency), clip [0.05, 3.00].&lt;/li&gt;
&lt;li&gt;Trainer: purged/embargoed CV, Optuna on buyer-relevant score, disjoint calibration.&lt;/li&gt;
&lt;li&gt;Replay report: chosen-side 1.5x/2.0x hit rate, MAE, time-to-hit, wrong-side rate — vs production baseline.&lt;/li&gt;
&lt;li&gt;Shadow (paper only): min 1 full live session before promotion talk.&lt;/li&gt;
&lt;li&gt;Promotion: only if replay AND shadow beat baseline. Evidence file paths log karo.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this matters for you
&lt;/h2&gt;

&lt;p&gt;Agar tum apna options model bana rahe ho, V1 ki galtiyon se bacho: ek model mat banao, nulls silent mat karo, shallow rakho, gate lagao. Direction ≠ Profit — V1 ka sabse bada lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ extended
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Meta-label kyun?&lt;/strong&gt; Mechanics side decide karta hai, model sirf "take trade?" batata hai — confusion khatam.&lt;br&gt;
&lt;strong&gt;Q: Truth-weight 0 rows exclude kyun?&lt;/strong&gt; Stale data model ko mislead karta hai, down-weight bhi galat.&lt;br&gt;
&lt;strong&gt;Q: AFT labels?&lt;/strong&gt; Censored survival — un-touched event ko exact time na do.&lt;br&gt;
&lt;strong&gt;Q: Scale_pos_weight cap 8?&lt;/strong&gt; Extreme imbalance calibration kharab karta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Options-Buyer V2 ek lessons-learned rebuild hai. V1 ke saare bugs (null corruption, overfit, CE/PE confusion, single-model) address kiye gaye. Abhi PF 0.53 hai par architecture honest hai. Time + data se improve hoga.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;V1 fail hua kyunki ek model ne direction aur execution dono seekhne ki koshish ki. V2 ne split kiya: underlying side decide karta hai, contract eligibility alag, aur multiple shallow heads CE/PE confusion khatam karte hain. Null-safety + overfit gate ne silent bugs rok diye. Architecture abhi losing hai (PF 0.53) par honest hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Action plan
&lt;/h2&gt;

&lt;p&gt;Agar tumhare paas V1 jaisa model hai jo fail ho raha:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ek model ki jagah multiple heads banao (CE alag, PE alag).&lt;/li&gt;
&lt;li&gt;Underlying mechanics ko side decide karne do, premium sirf validate karo.&lt;/li&gt;
&lt;li&gt;Har feature ka null policy likho, &lt;code&gt;fillna(0)&lt;/code&gt; hatao.&lt;/li&gt;
&lt;li&gt;Purged CV + overfit gate lagao.&lt;/li&gt;
&lt;li&gt;PF 1.0 se upar lane ke liye exit logic fix karo (SL widen, trailing).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ye 5 steps V1 ke 31.6% win ko fix karne ki foundation hain.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next article mei hum dekhenge signal gate design — kaise NO_TRADE bolna system ko bachata hai 90% false trades se. Wo V2 architecture ka final piece hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Agar tumhare model mei bhi CE/PE confusion hai, architecture rebuild karo V2 jaise. Ek model enough nahi hai complex options dynamics ke liye.&lt;/p&gt;

&lt;p&gt;The blueprint is open — build your own V2, dont repeat V1 mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  More From Shakti Tiwari
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Websites:&lt;/strong&gt; &lt;a href="https://shaktitiwari.github.io/shakti-tiwari-nse" rel="noopener noreferrer"&gt;shaktitiwari.github.io/shakti-tiwari-nse&lt;/a&gt; · &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;OptionTradingWithAI.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Books:&lt;/strong&gt; &lt;em&gt;Build Your Own AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0HBBFKDQF" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;) · &lt;em&gt;Option Trading with AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0H9ZNTBPK" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Community:&lt;/strong&gt; &lt;a href="https://discord.gg/shaktitiwari" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; · &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;about.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/shaktitiwari" rel="noopener noreferrer"&gt;GitHub/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏛️ &lt;strong&gt;Entity:&lt;/strong&gt; &lt;a href="https://www.wikidata.org/wiki/Q140689249" rel="noopener noreferrer"&gt;Wikidata Q140689249&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Purged Cross-Validation: overfitting kyun marta hai trading models ko</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:42:25 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/purged-cross-validation-overfitting-kyun-marta-hai-trading-models-ko-4ai2</link>
      <guid>https://dev.to/shaktitiwari/purged-cross-validation-overfitting-kyun-marta-hai-trading-models-ko-4ai2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;From the &lt;code&gt;options-buyer-system-v2&lt;/code&gt; blueprint — why plain k-fold silently destroys your trading backtest, and the 4-line fix that actually works.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;Tumne XGBoost train kiya, train AUC 0.92, test AUC 0.91 — "model sahi hai!" Aur fir live mei lagaya, 31.6% win rate, −₹90k loss. Kya hua? Answer: &lt;strong&gt;tumhari validation galat thi.&lt;/strong&gt; Financial data mei standard ML validation (k-fold, plain TimeSeriesSplit) silently leak karta hai, aur tumhe false confidence deta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why k-fold fails on time series
&lt;/h2&gt;

&lt;p&gt;Financial data sequential hai. k-fold rows shuffle karta hai, toh training mei ek row Tuesday 2 PM ki, test mei ek row Monday 10 AM ki — spatially distant par temporally adjacent. Worse: &lt;strong&gt;triple-barrier labels overlap karte hain.&lt;/strong&gt; Bar &lt;em&gt;t&lt;/em&gt; ka label 6 bars aage dekhta hai; training row &lt;em&gt;t+2&lt;/em&gt; us future ka "pata" hai. Model leak kar gaya, tumhe pata bhi nahi.&lt;/p&gt;

&lt;p&gt;Meri V1 history full thi "HIGH overfit" verdicts se — train AUC high, test flat. Plain &lt;code&gt;TimeSeriesSplit&lt;/code&gt; bhi adjacent windows bleed karta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Purged + Embargoed CV (the fix)
&lt;/h2&gt;

&lt;p&gt;Har test window &lt;code&gt;[t0, t1]&lt;/code&gt; ke liye:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Purge&lt;/strong&gt; — training rows jinki label window test se overlap karti hai, hatao.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embargo&lt;/strong&gt; — test window ke baad &lt;code&gt;max_training_horizon&lt;/code&gt; bars drop karo (ye bhi leak hain).
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;purged_embargo_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embargo_frac&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;splits&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&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;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;embargo_frac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&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;test&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;emb&lt;/span&gt;&lt;span class="p"&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;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;[&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="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;emb&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="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;splits&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="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;test&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;splits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ye 4-line function V1 ke overfit ko kaat deti hai. Overlapping labels i.i.d. nahi hote — purge+embargo honest split banata hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tune only when you have enough
&lt;/h2&gt;

&lt;p&gt;Optuna ek baar &lt;strong&gt;4 decisive rows&lt;/strong&gt; pe "jeet" gaya — statistically meaningless. Rule: decisive validation rows (non-abstained) &amp;lt; 30-50 ho toh date range widen karo ya symbol basket badhao, trial mat trust karo.&lt;/p&gt;

&lt;p&gt;Meri V1 mei Optuna "won" on 4 rows — us "best" params ne live mei 10s gateway timeout diya kyunki &lt;code&gt;n_estimators=1620&lt;/code&gt; hardcoded tha. Validation choti thi, overfit thi, phir bhi production mei chala diya.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three-way split, always
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train (fit) → validation (early stop + HP select)
            → disjoint calibration (sigmoid/isotonic)
            → test (untouched, final score only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;V1 ne validation aur calibration conflate kiya tha. Calibration set alag rakho — wo model ke raw probability ko real probability mei convert karta hai. Test set kabhi bhi training mei mat lena.&lt;/p&gt;

&lt;h2&gt;
  
  
  The promotion gate (hard rule)
&lt;/h2&gt;

&lt;p&gt;Har head ka &lt;code&gt;overfit_gap = train_metric − test_metric&lt;/code&gt;. &lt;strong&gt;&amp;gt; 0.15 → BLOCK.&lt;/strong&gt; Log har trial ka gap, sirf winner ka nahi. Promote sirf agar replay AND shadow (≥1 live session) dono baseline se beat karein buyer metrics pe: 1.5x/2.0x hit rate, MAE-before-hit, time-to-hit, wrong-side rate.&lt;/p&gt;

&lt;p&gt;Meri V1 mei &lt;code&gt;mean_overfit_gap &amp;gt;= 0.15&lt;/code&gt; pe FAIL verdict aata tha, par manually invoke hota tha. V2 mei ye DEFAULT gate hai — har head, har retrain, automatic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Underfit signal
&lt;/h2&gt;

&lt;p&gt;Agar balanced accuracy 50% ke paas hai aur overfit gap bhi chota hai — fix "more features add karo" nahi hai (V1 ki repeat mistake). Solution: event sampling, better labels, ya check karo ki truth-quality filter silently bahut kam signal bacha toh nahi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real example from my build
&lt;/h2&gt;

&lt;p&gt;Feature importance: top regression features &lt;code&gt;prev_return_5&lt;/code&gt; (21.3%), &lt;code&gt;range_low&lt;/code&gt; (15.1%), &lt;code&gt;atr&lt;/code&gt; (15.1%). Purged-CV ke baad test AUC 0.70-0.74 aaya (synthetic signal pe) — realistic, optimistic nahi. Overfit gap &amp;lt; 0.10 raha, so promote kiya gaya paper stage ke liye.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for options buyers
&lt;/h2&gt;

&lt;p&gt;CE/PE premium transformed signal hai. Agar tumne leak kiya validation mei, model "CE kharid" bolta hai kyunki usne future premium dekh liya — live mei wo future nahi hota. Purged CV ye rokta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: TimeSeriesSplit kyun nahi?&lt;/strong&gt; Adjacent windows bleed karte hain, partial leak.&lt;br&gt;
&lt;strong&gt;Q: Embargo kitna?&lt;/strong&gt; &lt;code&gt;max_training_horizon&lt;/code&gt; bars — label window length.&lt;br&gt;
&lt;strong&gt;Q: 30-50 rows kaise laoon?&lt;/strong&gt; Date range ya symbols badhao.&lt;br&gt;
&lt;strong&gt;Q: Calibration zaruri hai?&lt;/strong&gt; Haan, raw XGBoost probability calibrated nahi hoti.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Shuffled k-fold on time series — sabse badi.&lt;/li&gt;
&lt;li&gt;Optuna choti validation pe tune — false win.&lt;/li&gt;
&lt;li&gt;Validation=calibration conflate.&lt;/li&gt;
&lt;li&gt;Overfit gap ignore karke promote.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Backtest "looks good" bolta hai jab validation leak ho. Purged CV ek band-aid nahi, architecture ka hissa hai. V1 ke saare "HIGH overfit" verdicts isi se the, aur V2 mei ye default hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked numerical example
&lt;/h2&gt;

&lt;p&gt;Maan lo 100 bars hain, label horizon 6 bars. Plain k-fold 5 splits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split 1: train bars [0-79], test [80-99]&lt;/li&gt;
&lt;li&gt;Leak: bar 80 ka label bars 80-86 dekhta hai. Bar 79 (train mei) bhi bars 79-85 dekhta hai. Overlap! Bar 79 ko bar 80-85 ka partial future pata hai.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Purged version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split 1: test [80-99], embargo = 2 bars (6×0.02≈0.12→1-2)&lt;/li&gt;
&lt;li&gt;Purge bars [78-101] from train (test start 80 minus embargo 2 = 78, tak 101)&lt;/li&gt;
&lt;li&gt;Train ab [0-77] only. Bar 79/80 leak removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: test pe model ne jo bhi seekha, wo future nahi dekh paya. Honest score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Leak risk&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;k-fold (shuffled)&lt;/td&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;Never for time series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TimeSeriesSplit&lt;/td&gt;
&lt;td&gt;MEDIUM&lt;/td&gt;
&lt;td&gt;Fast baseline only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Purged+Embargo&lt;/td&gt;
&lt;td&gt;LOW&lt;/td&gt;
&lt;td&gt;Production research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walk-forward&lt;/td&gt;
&lt;td&gt;LOW&lt;/td&gt;
&lt;td&gt;Final validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I integrated in training
&lt;/h2&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;sklearn.model_selection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KFold&lt;/span&gt;  &lt;span class="c1"&gt;# NOT used
&lt;/span&gt;&lt;span class="n"&gt;splits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;purged_embargo_split&lt;/span&gt;&lt;span class="p"&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;X&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embargo_frac&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.02&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;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_idx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;splits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;X_tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_te&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;y_tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_te&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_idx&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="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_tr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_auc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;test_auc&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OVERFIT — block promotion&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;h2&gt;
  
  
  Epistemic note
&lt;/h2&gt;

&lt;p&gt;Meri synthetic test mei AUC 0.70-0.74 aaya. Real NIFTY data pe shayad kam aaye (market noise). Par gap &amp;lt; 0.10 raha — matlab model generalize kar raha hai, memorize nahi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why retail traders blow up
&lt;/h2&gt;

&lt;p&gt;Wo k-fold use karte hain, 90% accuracy dekhte hain, paid course bechte hain. Live mei 50% se kam. Purged CV unhe pehle hi bata deta: "tera model leak kar raha hai, real accuracy 55% hai."&lt;/p&gt;

&lt;h2&gt;
  
  
  My V1 autopsy
&lt;/h2&gt;

&lt;p&gt;V1 ke 3 models mei &lt;code&gt;mean_overfit_gap&lt;/code&gt; 0.40 tha kuch runs mei. Matlb train 0.85, test 0.45. Purged CV + shallow grid (depth 2-3) ne gap &amp;lt; 0.10 la diya V2 mei. Sirf architecture change, data same tha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Action plan for you
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Kisi bhi time-series model se pehle purge+embargo lagao.&lt;/li&gt;
&lt;li&gt;Optuna tabhi tune karo jab 30+ decisive rows hon.&lt;/li&gt;
&lt;li&gt;3-way split banaye rakho.&lt;/li&gt;
&lt;li&gt;Overfit gap &amp;gt; 0.15 pe promote band.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Extended FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Embargo fraction 0.02 kyun?&lt;/strong&gt; Label horizon ÷ total bars ka rough fraction. 6 bars / 300 bars ≈ 0.02.&lt;br&gt;
&lt;strong&gt;Q: Walk-forward vs purged CV?&lt;/strong&gt; Purged CV ek split strategy hai, walk-forward sequential retraining. Dono use karo — purged CV har walk-forward window ke andar.&lt;br&gt;
&lt;strong&gt;Q: Survival (AFT) labels pe bhi lagu?&lt;/strong&gt; Haan, censor horizon same purge logic.&lt;br&gt;
&lt;strong&gt;Q: Multi-asset?&lt;/strong&gt; Har symbol alag purge karo, mat mix karo (cross-symbol leak).&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger lesson
&lt;/h2&gt;

&lt;p&gt;ML mei sabse dangerous "looks too good" hai. Agar train/test dono 0.90+, doubt karo. Purged CV doubt ko proof mei convert karta hai. Meri V2 isliye survive karti hai kyunki har head pe gate hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Overfitting trading models ka silent killer hai. Plain validation use karke tum khud ko dhoka de rahe ho. Purged + embargoed CV 4-line fix hai jo V1 ke saare "HIGH overfit" verdicts ko rok deta hai. Lagao, warna live mei pachtayoge.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code: full purged CV with overfit gate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;purged_embargo_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embargo_frac&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&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;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;embargo_frac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&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;test&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;emb&lt;/span&gt;&lt;span class="p"&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;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;[&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="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;emb&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="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;out&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="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;test&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;out&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;train_with_gate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;gaps&lt;/span&gt; &lt;span class="o"&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;tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;te&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;purged_embargo_split&lt;/span&gt;&lt;span class="p"&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;X&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model_cls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;tr_auc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;te_auc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;te&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;te&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;gaps&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="n"&gt;tr_auc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;te_auc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mean_gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mean_gap&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROMOTE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mean_gap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ye exact pattern maine V2 mei use kiya. &lt;code&gt;mean_gap &amp;gt; 0.15&lt;/code&gt; → block. Simple, automatic, non-negotiable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Purged cross-validation trading models ko overfit hone se bachata hai by removing label-overlapping rows from training. Embargo + purge + 3-way split + overfit gate = honest backtest. Bina iske tumhara 90% accuracy dream live mei 50% ban jata hai. Lagao pehle, trading capital baad mei.&lt;/p&gt;

&lt;p&gt;Agar tumhare paas koi model hai jo backtest mei acha hai par live mei fail ho raha, sabse pehle validation check karo. 90% chance hai ki leak hai. Purged CV lagao, sachai samne aa jayegi.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not investment advice. SEBI compliance separate topic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next article mei hum dekhenge Options-Buyer V2 architecture — kaise ek model ki jagah multiple shallow heads use karke CE/PE confusion solve ki.&lt;/p&gt;

&lt;h2&gt;
  
  
  More From Shakti Tiwari
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Websites:&lt;/strong&gt; &lt;a href="https://shaktitiwari.github.io/shakti-tiwari-nse" rel="noopener noreferrer"&gt;shaktitiwari.github.io/shakti-tiwari-nse&lt;/a&gt; · &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;OptionTradingWithAI.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Books:&lt;/strong&gt; &lt;em&gt;Build Your Own AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0HBBFKDQF" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;) · &lt;em&gt;Option Trading with AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0H9ZNTBPK" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Community:&lt;/strong&gt; &lt;a href="https://discord.gg/shaktitiwari" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; · &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;about.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/shaktitiwari" rel="noopener noreferrer"&gt;GitHub/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏛️ &lt;strong&gt;Entity:&lt;/strong&gt; &lt;a href="https://www.wikidata.org/wiki/Q140689249" rel="noopener noreferrer"&gt;Wikidata Q140689249&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Skill Manager Router: 100+ Hermes skills ka confusion kaise avoid karein</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:42:07 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/skill-manager-router-100-hermes-skills-ka-confusion-kaise-avoid-karein-39i1</link>
      <guid>https://dev.to/shaktitiwari/skill-manager-router-100-hermes-skills-ka-confusion-kaise-avoid-karein-39i1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Notes from building the &lt;code&gt;skill-manager-router&lt;/code&gt; skill — how to stop a large AI skill library from becoming chaos. Agar tumhare paas bhi 50+ skills hain, ye architecture rescue karega.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Hermes 100+ skills ho sakte hain. Ek task aata hai ("traffic drop?") aur 5 skills activate ho jati hain — sab apna analysis dete hain, conflict ho jata hai. Output confusing ho jata hai, aur agent galat skill ke conclusion pe action leta hai. More skills ≠ more intelligence. Zyada skills sirf zyada confusion laate hain agar unhe manage na kiya jaye.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doctrine (6 rules)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;More skills do NOT automatically mean more intelligence.&lt;/strong&gt; Ek focused skill 10 generic skills se better hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Har skill ka clear domain, trigger, exclusions hona chahiye.&lt;/strong&gt; Agar ye nahi hai, skill incomplete hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One task = one primary skill.&lt;/strong&gt; Support max 2 ho sakte hain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support skills tools hain, competing decision-makers nahi.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duplicate skills merge ya deprecate karo.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value-na-dikhaye skill default inactive raho.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Mandatory metadata (har SKILL.md mei)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;exclusions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;support&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;conflicts&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;health&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;usage&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;success&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;last&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ye 16 fields har skill ko self-describing banate hain. Bina iske, router decide nahi kar sakta kon sa skill kis task ke liye hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Router decision tree (10 steps)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Parse user/task intent.&lt;/li&gt;
&lt;li&gt;Determine domain.&lt;/li&gt;
&lt;li&gt;Retrieve candidate skills from registry.&lt;/li&gt;
&lt;li&gt;Remove excluded/conflicting skills.&lt;/li&gt;
&lt;li&gt;Rank by: specificity &amp;gt; health &amp;gt; recency &amp;gt; success history &amp;gt; dependency availability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select exactly ONE PRIMARY.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Add max TWO SUPPORT skills only if required.&lt;/li&gt;
&lt;li&gt;Execute.&lt;/li&gt;
&lt;li&gt;Validate output.&lt;/li&gt;
&lt;li&gt;Record outcome (usage_count++, success/fail).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 6 sabse important hai — ek se zyada primary hone se conflict guaranteed hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context budget guard
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_PRIMARY_SKILLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;MAX_SUPPORT_SKILLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;MAX_ACTIVE_SKILL_DOCS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;MAX_DUPLICATE_CAPABILITY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load: summary metadata first; full SKILL.md only for SELECTED skills. Isse context window bachaata hai aur speed badhti hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conflict resolution
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Specialized skill beats general skill.&lt;/li&gt;
&lt;li&gt;Verified-healthy beats experimental.&lt;/li&gt;
&lt;li&gt;First-party-data skill beats estimate-only for owned-site decisions.&lt;/li&gt;
&lt;li&gt;Two skills cannot both issue final decisions on same subtask.&lt;/li&gt;
&lt;li&gt;If conflict remains → record UNCERTAINTY, do NOT blend incompatible conclusions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last point critical hai: agent kabhi bhi do alag skills ke contradictory answers mix nahi karega. Wo uncertainty report karega.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real script I built
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;registry_scan.py&lt;/code&gt; saare skills scan karta hai, metadata extract karta hai, &lt;code&gt;registry.json&lt;/code&gt; mei dump karta hai. Multi-domain skills flag karta hai consolidation ke liye.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;route.py &amp;lt;task&amp;gt;&lt;/code&gt; task leta hai, registry se PRIMARY + SUPPORT suggest karta hai. Ranking function specificity (trigger words match), health (active=3, experimental=2), aur success_count count karta hai.&lt;/p&gt;

&lt;p&gt;Maine 15 skills scan kiye — multiple skills same domain mei mile (publishing, seo variants). Ye consolidate karne ke candidates hain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: SEO routing
&lt;/h2&gt;

&lt;p&gt;| Task | Primary | Support |&lt;br&gt;
||||&lt;br&gt;
| Why did traffic drop? | gsc-intelligence | ga4-intelligence |&lt;br&gt;
| Why is this page slow? | technical-seo-guardian | none |&lt;br&gt;
| What article next? | content-opportunity-hunter | gsc-intelligence |&lt;br&gt;
| Write evidence article | citation-article-engine-v2 | content-opportunity-hunter |&lt;br&gt;
| Why not indexed? | indexing-sitemap-guardian | technical-seo-guardian |&lt;br&gt;
| Who cited us? | entity-citation-monitor | none |&lt;/p&gt;
&lt;h2&gt;
  
  
  Consolidation rule
&lt;/h2&gt;

&lt;p&gt;Jab &lt;code&gt;conflicts_with&lt;/code&gt; ya overlapping triggers mile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher &lt;code&gt;health_status&lt;/code&gt; + &lt;code&gt;success_count&lt;/code&gt; wale ko PRIMARY rakho.&lt;/li&gt;
&lt;li&gt;Doosre ko deprecate karo (&lt;code&gt;health_status: deprecated&lt;/code&gt;, &lt;code&gt;absorbed_into&lt;/code&gt; add karo).&lt;/li&gt;
&lt;li&gt;Pinned skill ko bina owner approval delete mat karo.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why this matters for autonomous agents
&lt;/h2&gt;

&lt;p&gt;Autonomous agent (jaise Hermes mesh) bina router ke random skills activate karega. Ek simple "article likho" task 5 skills trigger kar dega — seo-system, hindi-writer, publishing, governance, social. Router ensures: primary = seo-system, support = hindi-writer + social. Baaki exclude.&lt;/p&gt;
&lt;h2&gt;
  
  
  My experience
&lt;/h2&gt;

&lt;p&gt;Pehle mere paas 11 skills the bina metadata. Task aata tha toh sab respond karte the. Ab &lt;code&gt;registry_scan&lt;/code&gt; + &lt;code&gt;route.py&lt;/code&gt; se exactly 1 primary milta hai. Output clarity 10x better hai, aur agent faster decide karta hai.&lt;/p&gt;
&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Har skill mei 16 fields bharna zaroori hai?&lt;/strong&gt; Haan, at least name/domain/triggers/health_status. Baaki progressive fill karo.&lt;br&gt;
&lt;strong&gt;Q: Duplicate kaise identify karu?&lt;/strong&gt; &lt;code&gt;registry_scan.py&lt;/code&gt; multi-domain report deta hai. Manual bhi: triggers overlap dekho.&lt;br&gt;
&lt;strong&gt;Q: Router khud skill hai ya system?&lt;/strong&gt; Dono — ek skill jo doosre skills ko route karta hai.&lt;/p&gt;
&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;2 primary skills choose karna&lt;/strong&gt; — conflict guaranteed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata skip karna&lt;/strong&gt; — router blind ho jata hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deprecated skills delete kar dena&lt;/strong&gt; — pinned hone par nuksan. Inactive rakho.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context budget ignore karna&lt;/strong&gt; — saare SKILL.md load karo toh context bharta hai.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Infra note. Not trading/investment advice.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Deep dive: ranking function kaise kaam karti hai
&lt;/h2&gt;

&lt;p&gt;Router har candidate skill ko score karta hai. Formula simplified:&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;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trigger_matches&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health_rank&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;success_count&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recency_bonus&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;trigger_matches&lt;/code&gt;: task description mei jo words skill ke triggers/domain me hain, unka count. "traffic drop" task mei agar skill ke triggers mei "traffic", "ranking", "gsc" hain aur task mei "traffic" hai, +2.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;health_rank&lt;/code&gt;: active=3, verified=3, experimental=2, inactive=1, deprecated=0.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;success_count&lt;/code&gt;: historical wins. Zyada weight nahi (0.01) taaki new skill bhi chance paye.&lt;/p&gt;

&lt;p&gt;Final: top scorer = PRIMARY. Agle 2 (score &amp;gt; 1) = SUPPORT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registry.json structure (real)
&lt;/h2&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;"gsc-intelligence"&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;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"seo/gsc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"triggers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"traffic drop, ranking, query performance, ctr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"health_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_requirements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GSC_OAUTH_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&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;"citation-article-engine-v2"&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;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content/creation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"triggers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write article, draft, evidence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"health_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&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;Is JSON ko &lt;code&gt;route.py&lt;/code&gt; read karta hai. Har task pe rebuild nahi, sirf &lt;code&gt;registry_scan.py&lt;/code&gt; jab skills change hon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario: "NIFTY article likho with SEO"
&lt;/h2&gt;

&lt;p&gt;Bina router: seo-system + hindi-writer + publishing + social + nse-options-mcp sab trigger ho jayenge.&lt;br&gt;
Router ke saath:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain = "content creation + seo"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;citation-article-engine-v2&lt;/code&gt; (triggers: write article) → PRIMARY score highest&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;seo-system&lt;/code&gt; (triggers: seo, on-page) → SUPPORT&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hindi-writer&lt;/code&gt; (only if lang=hi requested) → SUPPORT or excluded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: 1 primary + max 2 support. Clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling to 100+ skills
&lt;/h2&gt;

&lt;p&gt;Jab 100 skills ho jayengi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MAX_ACTIVE_SKILL_DOCS = 3&lt;/code&gt; ensure karta hai ki context window mei sirf 3 full SKILL.md load hon.&lt;/li&gt;
&lt;li&gt;Baaki sirf metadata se match karo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;conflicts_with&lt;/code&gt; graph se circular dependencies detect karo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ye hi difference hai working agent aur chaotic agent mei.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kyun maine ye banaya
&lt;/h2&gt;

&lt;p&gt;Mera mesh 3 nodes pe chalta hai (Mac + 2 phones). Har node alag skills chala raha tha. Ek task "publish karo" pe Mac pe 5 skills activate ho rahe the, phones pe alag. Inconsistency thi. Router ne standardize kiya — har node same routing logic use karta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration guide (apne skills par lagao)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Har SKILL.md ke frontmatter mei 16 fields add karo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;registry_scan.py&lt;/code&gt; run karo → registry.json banega.&lt;/li&gt;
&lt;li&gt;Har task pe &lt;code&gt;route.py&lt;/code&gt; call karo pehle.&lt;/li&gt;
&lt;li&gt;Conflicts resolve karo (merge duplicates).&lt;/li&gt;
&lt;li&gt;Health status update karo har week.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2 ghante ka kaam, lifetime ki clarity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra note. Not trading/investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More From Shakti Tiwari
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Websites:&lt;/strong&gt; &lt;a href="https://shaktitiwari.github.io/shakti-tiwari-nse" rel="noopener noreferrer"&gt;shaktitiwari.github.io/shakti-tiwari-nse&lt;/a&gt; · &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;OptionTradingWithAI.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Books:&lt;/strong&gt; &lt;em&gt;Build Your Own AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0HBBFKDQF" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;) · &lt;em&gt;Option Trading with AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0H9ZNTBPK" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Community:&lt;/strong&gt; &lt;a href="https://discord.gg/shaktitiwari" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; · &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;about.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/shaktitiwari" rel="noopener noreferrer"&gt;GitHub/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏛️ &lt;strong&gt;Entity:&lt;/strong&gt; &lt;a href="https://www.wikidata.org/wiki/Q140689249" rel="noopener noreferrer"&gt;Wikidata Q140689249&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real implementation notes
&lt;/h2&gt;

&lt;p&gt;Maine &lt;code&gt;registry_scan.py&lt;/code&gt; pehli baar 15 skills pe chalaaya. Output mei multi-domain conflicts mili — &lt;code&gt;governed-publishing-discipline&lt;/code&gt; aur &lt;code&gt;governed-content-publishing&lt;/code&gt; dono "publishing/governance" domain mei. Consolidation candidate. &lt;code&gt;route.py&lt;/code&gt; ne "why did traffic drop" ke liye &lt;code&gt;gsc-intelligence&lt;/code&gt; ko top rank diya (trigger match + active health).&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling lessons
&lt;/h2&gt;

&lt;p&gt;Jab 50+ skills ho jayengi, manual metadata impossible hai. Isliye mandatory 16 fields frontmatter mei — &lt;code&gt;registry_scan&lt;/code&gt; automate kar leta hai. New skill add karte hi metadata bharo, warna router usse ignore karega (health_status default inactive).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is infrastructure, not optional
&lt;/h2&gt;

&lt;p&gt;Autonomous mesh (3 nodes) mei har node alag skills chala raha tha. Ek task pe inconsistent responses. Router ne standardize kiya — har node same logic. Is bina, distributed agent chaos hai.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra note. Not trading/investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final recommendation
&lt;/h2&gt;

&lt;p&gt;Agar tumhare paas 10+ skills hain, aaj hi &lt;code&gt;registry_scan&lt;/code&gt; banao. Conflicts visible honge. Ek primary rule enforce karo. 1 week mei agent ki clarity 10x ho jayegi.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra note. Not trading/investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Skill chaos silent killer hai. More skills = more confusion jab tak router na ho. 16 fields + 1 primary rule = scalable intelligence.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra note. Not trading/investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Build the router before you build the 50th skill. Order matters — chaos pehle aati hai, discipline baad mei nahi.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra note. Not trading/investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A well-routed skill library scales. A chaotic one collapses. Choose routing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra note. Not trading/investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One more principle
&lt;/h2&gt;

&lt;p&gt;Router khud ek skill hai jo doosre skills ko route karta hai — isliye ise "meta-skill" kehte hain. Iska apna koi conclusion nahi hota, sirf routing. Ye separation of concerns hai: decision skills decide karein, router sirf assign kare.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Free SEO MCP SuperStack: GSC + GA4 + PageSpeed bina paise ke</title>
      <dc:creator>shakti tiwari </dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:41:49 +0000</pubDate>
      <link>https://dev.to/shaktitiwari/free-seo-mcp-superstack-gsc-ga4-pagespeed-bina-paise-ke-58dd</link>
      <guid>https://dev.to/shaktitiwari/free-seo-mcp-superstack-gsc-ga4-pagespeed-bina-paise-ke-58dd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Walkthrough of the &lt;code&gt;seo-master-router&lt;/code&gt; + Free SEO MCP SuperStack skills I built. Goal: mostly-free SEO intelligence for a personal site using first-party data first. If you're tired of ₹10k/month SEO tools, ye architecture exactly wo hai jo chahiye.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problem statement
&lt;/h2&gt;

&lt;p&gt;Personal site ya blog chalate ho? Har SEO tool ₹999-₹5000/month mangta hai. Ahrefs, Semrush, Moz — sab expensive. Par sach yeh hai: &lt;strong&gt;Google khud free tools deta hai&lt;/strong&gt; jo 90% use-cases cover karte hain. Bas unhe sahi tarah connect karna aur route karna aata hona chahiye. Main ₹0 mei poora SEO intelligence stack chalata hoon — yahan dikha raha hoon kaise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free stack (real, ₹0)
&lt;/h2&gt;

&lt;p&gt;| Layer | Tool | Cost | Kya deta hai |&lt;br&gt;
|-|||--|&lt;br&gt;
| Search demand/rankings | Google Search Console API | Free (quota) | Queries, pages, clicks, impressions, CTR, position |&lt;br&gt;
| Analytics | Google Analytics 4 | Free | Sessions, landing pages, engagement |&lt;br&gt;
| Performance | PageSpeed Insights + CrUX | Free | Core Web Vitals, lab + field data |&lt;br&gt;
| Indexing | GSC URL inspection + sitemap | Free | Indexing state, canonical, crawl errors |&lt;br&gt;
| Citations | Web search + exact-title queries | Free | Third-party pickups, mentions |&lt;br&gt;
| Content engine | Citation Article Engine V2 (local) | Free | Research, score, draft |&lt;/p&gt;

&lt;p&gt;Total cost: &lt;strong&gt;₹0&lt;/strong&gt;. Sirf time lagta hai setup mei.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seo-master-router
 ├─ gsc-intelligence        (query/page perf, CTR, cannibalization)
 ├─ ga4-intelligence        (landing-page engagement, conversions)
 ├─ technical-seo-guardian (PageSpeed/CrUX, schema, canonical)
 ├─ indexing-guardian      (indexing/sitemap/canonical)
 ├─ entity-citation-monitor (external mentions, pickups)
 └─ citation-article-engine-v2 (research, score, draft)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key principle: &lt;strong&gt;har SEO task → exactly one PRIMARY skill + max 2 SUPPORT&lt;/strong&gt;. Router khud analysis nahi karta, sirf route karta hai. Isse confusion nahi hota ki kon sa tool kya bol raha hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing table
&lt;/h2&gt;

&lt;p&gt;| Task | Primary | Support |&lt;br&gt;
||||&lt;br&gt;
| "Why did traffic drop?" | gsc-intelligence | ga4-intelligence |&lt;br&gt;
| "Why is this page slow?" | technical-seo-guardian | none |&lt;br&gt;
| "What article next?" | content-opportunity-hunter | gsc-intelligence |&lt;br&gt;
| "Write evidence article" | citation-article-engine-v2 | content-opportunity-hunter |&lt;br&gt;
| "Why not indexed?" | indexing-sitemap-guardian | technical-seo-guardian |&lt;br&gt;
| "Who cited us?" | entity-citation-monitor | none |&lt;/p&gt;

&lt;h2&gt;
  
  
  Core skill contracts
&lt;/h2&gt;

&lt;p&gt;Har skill ka clear "must do / must NOT do" hai:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gsc-intelligence&lt;/code&gt;: reads query/page performance. &lt;strong&gt;Must NOT write articles.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ga4-intelligence&lt;/code&gt;: measures user behavior. &lt;strong&gt;Must NOT infer rankings.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content-opportunity-hunter&lt;/code&gt;: prioritizes topics from real data. &lt;strong&gt;Must NOT publish directly.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;citation-article-engine-v2&lt;/code&gt;: researches, scores, drafts. &lt;strong&gt;Must NOT fabricate data.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;technical-seo-guardian&lt;/code&gt;: performance/schema/crawl. &lt;strong&gt;Must NOT rewrite content unless asked.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;indexing-sitemap-guardian&lt;/code&gt;: indexing diagnostics. &lt;strong&gt;Must NOT mass-request indexing.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest gap (verify-first)
&lt;/h2&gt;

&lt;p&gt;GSC/GA4 OAuth tokens mere paas nahi hain abhi. Isliye &lt;code&gt;gsc-intelligence&lt;/code&gt; aur &lt;code&gt;ga4-intelligence&lt;/code&gt; &lt;strong&gt;DEV.to API fallback&lt;/strong&gt; use karte hain — published articles ke views ek proxy signal dete hain (real engagement, par site-level nahi). Jab token milenge, live GSC data aayega. Credential gate ensure karta hai: token nahi → &lt;strong&gt;UNKNOWN report&lt;/strong&gt;, fabricate nahi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why free-first beats paid for personal sites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First-party data sabse accurate hai.&lt;/strong&gt; GSC tumhe apni site ka exact impression/CTR batata hai — third-party tools estimate dete hain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock-in.&lt;/strong&gt; API badal do, architecture wahi rehti hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local content engine.&lt;/strong&gt; Citation Article Engine V2 local Hermes skill hai — koi SaaS cost nahi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning.&lt;/strong&gt; Paid tool "just works" par andar kya ho raha hai nahi dikhata. Free stack samajhne pe majboor karta hai.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  My actual SEO workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monday:&lt;/strong&gt; gsc-intelligence se top query drops dekho.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuesday:&lt;/strong&gt; ga4-intelligence se landing-page engagement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wednesday:&lt;/strong&gt; technical-seo-guardian se PageSpeed check (free, token nahi chahiye).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thursday:&lt;/strong&gt; content-opportunity-hunter next article decide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friday:&lt;/strong&gt; citation-article-engine-v2 draft likho (evidence-led, no fabrication).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Comparison: free vs paid
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Free Stack&lt;/th&gt;
&lt;th&gt;Paid (Ahrefs etc)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;₹0&lt;/td&gt;
&lt;td&gt;₹1500-5000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your own data&lt;/td&gt;
&lt;td&gt;Full (GSC)&lt;/td&gt;
&lt;td&gt;Estimated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Competitor research&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data ownership&lt;/td&gt;
&lt;td&gt;Yours&lt;/td&gt;
&lt;td&gt;Vendor's&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Personal site ke liye free stack sufficient hai. Competitor spying chahiye toh paid consider karo.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: GSC API ke liye credit card?&lt;/strong&gt; Nahi. Free Google account + property verify karo.&lt;br&gt;
&lt;strong&gt;Q: PageSpeed Insights free?&lt;/strong&gt; Haan, API key bhi nahi chahiye basic use.&lt;br&gt;
&lt;strong&gt;Q: DEV.to fallback reliable?&lt;/strong&gt; Partial. Apni site ka nahi, sirf DEV.to content ka. GSC token aane se real.&lt;br&gt;
&lt;strong&gt;Q: Setup time?&lt;/strong&gt; ~2 ghante agar GSC property verified hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sab tools ek saath chalana&lt;/strong&gt; — confusion. Router use karo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GSC ignore karke paid khareedna&lt;/strong&gt; — ₹0 mei mil raha hai.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mass indexing requests&lt;/strong&gt; — spam flag. Patient raho.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fabricated numbers&lt;/strong&gt; — kabhi mat karo.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What you'll learn building this
&lt;/h2&gt;

&lt;p&gt;Mujhe samjha: SEO tools kaam aati hain par &lt;strong&gt;discipline&lt;/strong&gt; jeetati hai. Router har din sahi skill ko sahi data ke saath chalata hai — bina overwhelm hue. Free stack ne mujhe 750+ articles publish karne mei help ki, bina ek rupee tool par kharch kiye.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not a Google-partner claim.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step: apna free stack setup karo
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console property verify karo&lt;/strong&gt; — domain ya URL prefix method. 2 minute ka kaam.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GA4 property banao&lt;/strong&gt; — Data Streams mei web stream add karo, tracking code site mei dalo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service account banana (for API)&lt;/strong&gt; — Google Cloud Console mei project, enable Search Console API + GA4 API, service account key download karo (JSON).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential gate set karo&lt;/strong&gt; — &lt;code&gt;.env&lt;/code&gt; mei &lt;code&gt;GSC_OAUTH_TOKEN&lt;/code&gt; aur &lt;code&gt;GA4_CREDENTIALS&lt;/code&gt; rakho.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Router skill load karo&lt;/strong&gt; — &lt;code&gt;seo-master-router&lt;/code&gt; SKILL.md ke routing table ke mutabik task classify karo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pehla analysis chalao&lt;/strong&gt; — "Why did traffic drop?" → gsc-intelligence primary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback samjho&lt;/strong&gt; — token nahi hai toh DEV.to proxy se start karo, bad mei upgrade.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real-world example: ek drop diagnose kiya
&lt;/h2&gt;

&lt;p&gt;Maan lo tumhari site pe organic traffic 30% gira. Free stack mei:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;gsc-intelligence&lt;/strong&gt; batayega: kaunsi queries impressions rakh rahi hain par clicks gir gaye (CTR problem) ya position gir gayi (ranking problem).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ga4-intelligence&lt;/strong&gt; batayega: landing page par bounce rate badha ya nahi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;technical-seo-guardian&lt;/strong&gt; batayega: PageSpeed gir gaya ya crawl errors aaye.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teen alag skills alag angle dete hain — router confusion nahi karta. Isse tum确切 decide kar paate ho: content fix chahiye ya technical fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kyun main paid tools nahi use karta
&lt;/h2&gt;

&lt;p&gt;Personal blog + 750 articles ka portfolio hai mera. Paid tool se jo value milegi (competitor spying, backlink audit) wo mere use-case mei marginal hai. GSC + GA4 + PageSpeed se jo chahiye wo mil jata hai: apni rankings, apni engagement, apni speed. Bacha ₹3000/month better books/tools mei lagta hai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling the stack
&lt;/h2&gt;

&lt;p&gt;Jab site badhe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cron jobs&lt;/strong&gt; se daily GSC pull karo, DuckDB mei store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity-citation-monitor&lt;/strong&gt; se har week apne mentions track karo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citation Article Engine V2&lt;/strong&gt; se content gaps fill karo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ye sab local + free hai. Cloud cost sirf GitHub Pages hosting (jo bhi free hai).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not a Google-partner claim.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More From Shakti Tiwari
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Websites:&lt;/strong&gt; &lt;a href="https://shaktitiwari.github.io/shakti-tiwari-nse" rel="noopener noreferrer"&gt;shaktitiwari.github.io/shakti-tiwari-nse&lt;/a&gt; · &lt;a href="https://optiontradingwithai.in" rel="noopener noreferrer"&gt;OptionTradingWithAI.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Books:&lt;/strong&gt; &lt;em&gt;Build Your Own AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0HBBFKDQF" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;) · &lt;em&gt;Option Trading with AI&lt;/em&gt; (&lt;a href="https://www.amazon.in/dp/B0H9ZNTBPK" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Community:&lt;/strong&gt; &lt;a href="https://discord.gg/shaktitiwari" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; · &lt;a href="https://x.com/shaktitiwari" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://about.me/shaktitiwari" rel="noopener noreferrer"&gt;about.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/shaktitiwari" rel="noopener noreferrer"&gt;GitHub/shaktitiwari&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🏛️ &lt;strong&gt;Entity:&lt;/strong&gt; &lt;a href="https://www.wikidata.org/wiki/Q140689249" rel="noopener noreferrer"&gt;Wikidata Q140689249&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Extended deep-dive: GSC query mining
&lt;/h2&gt;

&lt;p&gt;Jab GSC token milega, sabse pehla kaam: top queries by impressions jinki position 11-20 hai (page 2). Ye "page 2 opportunities" hain — thoda on-page tweak se page 1 mei aa jayengi. GSC API deta hai &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;clicks&lt;/code&gt;, &lt;code&gt;impressions&lt;/code&gt;, &lt;code&gt;ctr&lt;/code&gt;, &lt;code&gt;position&lt;/code&gt;. Isko DuckDB mei store karo, weekly diff nikalo.&lt;/p&gt;

&lt;h2&gt;
  
  
  GA4 conversion events
&lt;/h2&gt;

&lt;p&gt;GA4 mei "book_click", "discord_join", "github_visit" events set karo. Tab ga4-intelligence actual funnel dikhayega: kitne readers book tak pahuche. Free hai, bas 1 baar setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexing guardian
&lt;/h2&gt;

&lt;p&gt;GSC URL inspection se check karo ki important pages indexed hain ya nahi. Sitemap submit karo. Agar "Discovered - not indexed" aa raha hai, content thin hai — citation-article-engine-v2 se expand karo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a SuperStack
&lt;/h2&gt;

&lt;p&gt;Alag alag tools nahi, ek router unhe coordinate karta hai. GSC batayega "traffic gira", GA4 batayega "landing page bounce badha", technical batayega "speed giri". Sab milke root cause dikhate hain.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not a Google-partner claim.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;Free SEO stack = GSC + GA4 + PageSpeed + local content engine. ₹0 mei poora intelligence. Router confusion rokta hai. GSC/GA4 tokens aane se live data aayega, abhi DEV.to fallback chal raha hai. Build it, 2 ghante mei tayyar.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not a Google-partner claim.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Agar tum ₹3000/month SEO tool par soch rahe ho, pehle ye free stack try karo. 90% use-cases cover ho jayengi. Baaki 10% ke liye paid bad mei consider karna.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Research only. Not a Google-partner claim.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;SEO free ho sakta hai, bas structure chahiye. Router + free tools = winning combo.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
