<?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: MarketDeck</title>
    <description>The latest articles on DEV Community by MarketDeck (marketdeck).</description>
    <link>https://dev.to/marketdeck</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%2Forganization%2Fprofile_image%2F15226%2F49554f13-00f8-4f9e-abd1-a54ff0e037fc.png</url>
      <title>DEV Community: MarketDeck</title>
      <link>https://dev.to/marketdeck</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marketdeck"/>
    <language>en</language>
    <item>
      <title>Two Correct Revenue Figures, One Wrong Chart: Building Source-Aware Financial Data in Python</title>
      <dc:creator>Sarthak Verma</dc:creator>
      <pubDate>Fri, 23 Oct 2026 06:16:20 +0000</pubDate>
      <link>https://dev.to/marketdeck/two-correct-revenue-figures-one-wrong-chart-building-source-aware-financial-data-in-python-2ikm</link>
      <guid>https://dev.to/marketdeck/two-correct-revenue-figures-one-wrong-chart-building-source-aware-financial-data-in-python-2ikm</guid>
      <description>&lt;p&gt;Imagine a dashboard reporting that a company's revenue grew &lt;strong&gt;75%&lt;/strong&gt; in one year. The numbers came from financial statements. The arithmetic checks out. Nothing crashed.&lt;/p&gt;

&lt;p&gt;There's just one problem: &lt;strong&gt;the dashboard compared standalone revenue from one year with consolidated revenue from the next.&lt;/strong&gt; The resulting growth rate describes neither business consistently.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;&lt;a href="https://marketdeck.in/" rel="noopener noreferrer"&gt;MarketDeck&lt;/a&gt;&lt;/strong&gt;, we're building a connected financial research workspace for Indian markets, including filing-backed company research, screening, interactive charts, and quantitative analysis. One design principle behind our company research is simple: a financial figure is not meaningful without its reporting context.&lt;/p&gt;

&lt;p&gt;This article walks through a small, runnable Python example of that principle. It's a standalone teaching example using &lt;em&gt;fictional numbers&lt;/em&gt;, &lt;strong&gt;not&lt;/strong&gt; a copy of MarketDeck's production code or a claim about any real listed company.&lt;/p&gt;

&lt;h2&gt;
  
  
  The invisible data bug
&lt;/h2&gt;

&lt;p&gt;Suppose a fictional company reports the following revenue figures (all in ₹ crore):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reporting year&lt;/th&gt;
&lt;th&gt;Accounting basis&lt;/th&gt;
&lt;th&gt;Revenue (₹ crore)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FY2024&lt;/td&gt;
&lt;td&gt;Standalone&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FY2024&lt;/td&gt;
&lt;td&gt;Consolidated&lt;/td&gt;
&lt;td&gt;1,100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FY2025&lt;/td&gt;
&lt;td&gt;Consolidated&lt;/td&gt;
&lt;td&gt;1,400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two tempting calculations produce completely different stories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Wrong comparison:&lt;/strong&gt; 1,400 ÷ 800 − 1 = &lt;strong&gt;75% growth&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Like-for-like comparison:&lt;/strong&gt; 1,400 ÷ 1,100 − 1 = &lt;strong&gt;27.27% growth&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first calculation mixes reporting bases; the second compares consolidated annual revenue with consolidated annual revenue. Even the second figure would need further review if the company's consolidation perimeter or accounting policies changed significantly.&lt;/p&gt;

&lt;p&gt;This isn't an exotic edge case. Indian listed companies can file results in both standalone and consolidated formats. NSE also supports structured XBRL financial reporting, where units, periods and taxonomy elements matter. A dashboard that discards those fields loses information it needs to compare figures responsibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat every number as a record, not a float
&lt;/h2&gt;

&lt;p&gt;The usual toy data model is something like:&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;revenue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's fine for a one-line calculation. For a research application, it is under-specified.&lt;/p&gt;

&lt;p&gt;We should be able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which issuer and metric does this number belong to?&lt;/li&gt;
&lt;li&gt;Which fiscal period does it represent: quarter, full year or trailing twelve months?&lt;/li&gt;
&lt;li&gt;Is it &lt;strong&gt;standalone&lt;/strong&gt; or &lt;strong&gt;consolidated&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Which currency and unit are being used?&lt;/li&gt;
&lt;li&gt;When did the result become publicly available?&lt;/li&gt;
&lt;li&gt;Which filing or record supports it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's one deliberately minimal Python model that keeps those answers attached. It uses only the standard library and runs on Python 3.10+.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Standalone, synthetic demonstration for the MarketDeck DEV article.

Not production MarketDeck source code.
Python 3.10+; no external dependencies.
&lt;/span&gt;&lt;span class="sh"&gt;"""&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="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;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;issuer&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;metric&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;period_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
    &lt;span class="n"&gt;period_type&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;basis&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;units&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;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
    &lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;source_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_fact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;facts&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;issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;period_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_of&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;as_of&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcoffset&lt;/span&gt;&lt;span class="p"&gt;()&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;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;as_of must have a timezone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;eligible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issuer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;issuer&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;metric&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;period_end&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;period_end&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;period_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;basis&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;units&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INR crore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;as_of&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;eligible&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;LookupError&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 comparable fact known by this date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Real datasets need explicit revision handling.
&lt;/span&gt;    &lt;span class="k"&gt;return&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;eligible&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="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;facts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FictionalCo&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;revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&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="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FY&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;standalone&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;INR crore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;800&lt;/span&gt;&lt;span class="sh"&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;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-05-10T18:00:00+05:30&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;DEMO-2024-ST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FictionalCo&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;revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&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="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FY&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;consolidated&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;INR crore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1100&lt;/span&gt;&lt;span class="sh"&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;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-05-15T18:00:00+05:30&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;DEMO-2024-CON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FictionalCo&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;revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&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="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FY&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;consolidated&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;INR crore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1400&lt;/span&gt;&lt;span class="sh"&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;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-05-20T18:00:00+05:30&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;DEMO-2025-CON&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;as_of&lt;/span&gt; &lt;span class="o"&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;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-06-01T12:00:00+05:30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;prior&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_fact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FictionalCo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;period_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&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="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consolidated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;as_of&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_fact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FictionalCo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;period_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&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="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consolidated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;as_of&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;growth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&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="o"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100&lt;/span&gt;&lt;span class="sh"&gt;"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Comparable revenue growth: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;growth&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="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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sources: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_id&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;# Check that unpublished information is excluded.
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;find_fact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FictionalCo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;period_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&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="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consolidated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="o"&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;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-04-01T12:00:00+05:30&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;except&lt;/span&gt; &lt;span class="nb"&gt;LookupError&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;Early query: correctly rejected &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;an unpublished figure&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Look-ahead check failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;naive_growth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1400&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="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;800&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="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;naive_growth&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;75&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;growth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quantize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.01&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="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;27.27&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Comparable revenue growth: 27.27%
Sources: DEMO-2024-CON -&amp;gt; DEMO-2025-CON
Early query: correctly rejected an unpublished figure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All &lt;code&gt;DEMO-*&lt;/code&gt; identifiers and numbers above are invented for this tutorial. They are &lt;strong&gt;not&lt;/strong&gt; exchange filing identifiers or actual stock-market data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the code protects against
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Mixed accounting bases
&lt;/h3&gt;

&lt;p&gt;The selector requires the same &lt;code&gt;basis&lt;/code&gt; for both years.&lt;/p&gt;

&lt;p&gt;A standalone figure cannot quietly enter a consolidated comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Mixed reporting periods
&lt;/h3&gt;

&lt;p&gt;The code asks explicitly for an FY observation and matches a specified fiscal year-end.&lt;/p&gt;

&lt;p&gt;In a production system, quarters, year-to-date amounts and trailing-twelve-month values need equally explicit period types and duration checks.&lt;/p&gt;

&lt;p&gt;The same date alone is not sufficient to establish that two figures have the same reporting duration.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Mixed units
&lt;/h3&gt;

&lt;p&gt;The example requires a declared unit.&lt;/p&gt;

&lt;p&gt;A production implementation should also normalise values carefully because &lt;code&gt;₹ lakh&lt;/code&gt;, &lt;code&gt;₹ crore&lt;/code&gt;, &lt;code&gt;INR&lt;/code&gt; and &lt;code&gt;USD&lt;/code&gt; are not interchangeable.&lt;/p&gt;

&lt;p&gt;The unit filter here is deliberately strict rather than performing conversions silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Look-ahead leakage
&lt;/h3&gt;

&lt;p&gt;The condition:&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;keeps figures unavailable at the simulated query time out of the result.&lt;/p&gt;

&lt;p&gt;Fiscal year-end is &lt;strong&gt;not&lt;/strong&gt; the same thing as publication date.&lt;/p&gt;

&lt;p&gt;For event-driven simulations, the exact publication time and the next executable market session can both matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Untraceable output
&lt;/h3&gt;

&lt;p&gt;The calculation returns source identifiers beside the result.&lt;/p&gt;

&lt;p&gt;In a real application, those identifiers should resolve to specific original filings and, where possible, the relevant financial statements and accounting elements.&lt;/p&gt;

&lt;p&gt;There's an important limitation here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A source link is necessary, but not enough.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A restatement, changed consolidation perimeter, incorrect taxonomy mapping or missing filing could still make a number economically non-comparable.&lt;/p&gt;

&lt;p&gt;If your system ingests amendments, it needs explicit revision and supersession handling.&lt;/p&gt;

&lt;p&gt;Simply choosing the latest available record works only if your dataset models and validates that policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How that changes the UI
&lt;/h2&gt;

&lt;p&gt;A trustworthy financial dashboard shouldn't make readers reverse-engineer a headline metric.&lt;/p&gt;

&lt;p&gt;Alongside a growth percentage, I'd want to display something like this:&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;Research UI should explain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Value&lt;/td&gt;
&lt;td&gt;27.27% (illustrative)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comparison&lt;/td&gt;
&lt;td&gt;FY2025 vs FY2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basis&lt;/td&gt;
&lt;td&gt;Consolidated → Consolidated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inputs&lt;/td&gt;
&lt;td&gt;₹1,400 crore and ₹1,100 crore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;Dates both records were first public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence&lt;/td&gt;
&lt;td&gt;Original filings and financial-statement elements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exceptions&lt;/td&gt;
&lt;td&gt;Missing data, revisions or comparability warnings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This isn't about making interfaces unnecessarily complicated.&lt;/p&gt;

&lt;p&gt;It's about giving users a clean headline &lt;strong&gt;and&lt;/strong&gt; a way to inspect the evidence when the detail matters.&lt;/p&gt;

&lt;p&gt;At MarketDeck, our existing &lt;a href="https://marketdeck.in/screener/research-terminal/" rel="noopener noreferrer"&gt;Research Terminal&lt;/a&gt; connects financial history with reporting context, valuation, peer research and source tracing.&lt;/p&gt;

&lt;p&gt;Our company research also includes Source Trace ratio cards designed to show formulas, periods and missing-input explanations.&lt;/p&gt;

&lt;p&gt;The example in this post is intentionally simplified. It illustrates the design reasoning rather than documenting our internal implementation line-for-line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three test cases I'd add before shipping
&lt;/h2&gt;

&lt;p&gt;The script already tests one incorrect accounting comparison and one too-early query.&lt;/p&gt;

&lt;p&gt;A production financial data pipeline needs more checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Restatement test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A later amended filing must not overwrite the earlier as-of history retroactively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Context test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Annual, quarterly, year-to-date and trailing values must not become substitutes simply because their end dates align.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Provenance test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Missing source records, mismatched currencies or incompatible reporting bases must produce a visible missing or uncertain result—not a fabricated zero or silent fallback.&lt;/p&gt;

&lt;p&gt;Also worth testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fiscal-year changes&lt;/li&gt;
&lt;li&gt;Mergers and acquisitions&lt;/li&gt;
&lt;li&gt;Revised taxonomy mappings&lt;/li&gt;
&lt;li&gt;Stale observation windows&lt;/li&gt;
&lt;li&gt;Timezone handling around market close&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The aim is not to promise that every financial number can be made perfectly comparable.&lt;/p&gt;

&lt;p&gt;It is to make &lt;strong&gt;the conditions under which it is comparable inspectable.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;It's easy to spend most of our time polishing the front end of a financial dashboard: chart animations, filters and fast search.&lt;/p&gt;

&lt;p&gt;Those things matter.&lt;/p&gt;

&lt;p&gt;But a chart can be beautifully rendered and still answer the wrong question.&lt;/p&gt;

&lt;p&gt;Once a system tracks &lt;strong&gt;what was reported, for which period, on which basis, when it became known and where it came from&lt;/strong&gt;, the product becomes more useful for far more than charts.&lt;/p&gt;

&lt;p&gt;The same structured evidence can support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human financial research&lt;/li&gt;
&lt;li&gt;Company screening&lt;/li&gt;
&lt;li&gt;Reproducible quantitative experiments&lt;/li&gt;
&lt;li&gt;Historical comparisons&lt;/li&gt;
&lt;li&gt;AI-assisted interfaces grounded in verifiable financial information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the direction we're building toward at MarketDeck: accessible quantitative research, with the context needed to question the output.&lt;/p&gt;

&lt;p&gt;Our broader AI-connected experimentation and data-access vision remains future work, not something this tutorial claims is already live.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explore MarketDeck — Financial Research Beyond the Numbers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you're interested in financial markets, quantitative analysis or building data-driven applications, take a look at &lt;a href="https://marketdeck.in/" rel="noopener noreferrer"&gt;MarketDeck&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're building an India-first quantitative financial research platform that brings together company fundamentals, stock screening, advanced charting, derivatives research, portfolio analytics and interactive mathematical tools.&lt;/p&gt;

&lt;p&gt;Instead of switching between disconnected websites, spreadsheets and analytical tools, our goal is to make financial research more connected, transparent and accessible.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you can explore today
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🔍 &lt;a href="https://marketdeck.in/screener/" rel="noopener noreferrer"&gt;MarketDeck Screener&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Explore Indian listed companies, financial metrics, research filters and company fundamentals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📊 &lt;a href="https://marketdeck.in/screener/research-terminal/" rel="noopener noreferrer"&gt;Research Terminal&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Investigate financial history, reporting periods, valuation context, peer comparisons and source-linked research.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📈 &lt;a href="https://marketdeck.in/" rel="noopener noreferrer"&gt;MarketDeck&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Explore charting, derivatives research, portfolio analytics and interactive learning experiences from one connected platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📚 &lt;a href="https://marketdeck.in/research-standards/" rel="noopener noreferrer"&gt;Research Standards&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Learn more about our approach to methodology, source transparency, research limitations and financial data quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where we're going next
&lt;/h3&gt;

&lt;p&gt;Our long-term vision is to build an integrated quantitative research workspace with AI-assisted research, programmable computation and permissioned financial data services.&lt;/p&gt;

&lt;p&gt;These broader capabilities are part of our development direction and are not all available as a finished end-to-end workflow today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MarketDeck is currently free to use.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  👉 &lt;a href="https://marketdeck.in/" rel="noopener noreferrer"&gt;Explore MarketDeck — https://marketdeck.in/&lt;/a&gt;
&lt;/h3&gt;




&lt;h2&gt;
  
  
  One question for fellow developers
&lt;/h2&gt;

&lt;p&gt;If you build data-heavy products, what's the most subtle &lt;strong&gt;"valid data, wrong interpretation"&lt;/strong&gt; bug you've encountered?&lt;/p&gt;

&lt;p&gt;I'd love to hear how other developers deal with source tracking, data revisions and inconsistent reporting formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.nseindia.com/static/companies-listing/xbrl-information" rel="noopener noreferrer"&gt;NSE: XBRL filing information&lt;/a&gt; — Financial-report taxonomy, period and unit context.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.nseindia.com/companies-listing/corporate-filings-announcements" rel="noopener noreferrer"&gt;NSE: Corporate filings announcements&lt;/a&gt; — Filing and dissemination timestamps.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nsearchives.nseindia.com/web/sites/default/files/inline-files/NSE_Circular_02042025_0.pdf" rel="noopener noreferrer"&gt;NSE: Integrated Financial Filing circular (April 2025)&lt;/a&gt; — Evolving PDF and XBRL filing processes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketdeck.in/screener/research-terminal/" rel="noopener noreferrer"&gt;MarketDeck Research Terminal&lt;/a&gt; — Company research with sources and reporting context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Educational and software-engineering content only. No investment recommendations. All demonstration amounts are fictional.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>dataengineering</category>
      <category>finance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>10 Stocks, One Hidden Risk: Build a Portfolio Correlation Lab in Python</title>
      <dc:creator>Sarthak Verma</dc:creator>
      <pubDate>Sat, 10 Oct 2026 18:13:53 +0000</pubDate>
      <link>https://dev.to/marketdeck/10-stocks-one-hidden-risk-build-a-portfolio-correlation-lab-in-python-43o2</link>
      <guid>https://dev.to/marketdeck/10-stocks-one-hidden-risk-build-a-portfolio-correlation-lab-in-python-43o2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ten stocks can still behave like one crowded trade.&lt;/strong&gt; The number of positions in your portfolio does not tell you how much risk they share.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Picture a portfolio containing a bank, an IT company, an automaker, a consumer brand, and six other businesses. Ten tickers. Several industries. It feels diversified.&lt;/p&gt;

&lt;p&gt;Now imagine a broad market selloff in which most of those stocks fall together. You diversified the &lt;em&gt;company names&lt;/em&gt;, but did you diversify the forces moving their prices?&lt;/p&gt;

&lt;p&gt;That's the problem we'll investigate. We'll start with a simple experiment you can run in &lt;a href="https://marketdeck.in/screener/learning/books/expected-returns/" rel="noopener noreferrer"&gt;MarketDeck's interactive learning lab&lt;/a&gt;, rebuild the mathematics in Python, and finish with a more useful way to question portfolio concentration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll learn:&lt;/strong&gt; why correlations matter, how to calculate portfolio volatility from a covariance matrix, and what an "effective number of exposures" really means. Everything below uses hypothetical numbers; you won't need a brokerage account or market-data API.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Two stocks, one question
&lt;/h2&gt;

&lt;p&gt;Suppose you hold two fictional stocks, A and B. Each has an &lt;strong&gt;assumed annual volatility of 20%&lt;/strong&gt;, and each receives &lt;strong&gt;50% of your portfolio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Only one thing changes between experiments: &lt;em&gt;correlation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Correlation describes how two return series tend to move relative to each other, on a scale from -1 to +1. A value near +1 indicates strong positive co-movement; a value near 0 means little &lt;em&gt;linear&lt;/em&gt; relationship. It does not mean the assets are risk-free or genuinely independent in every statistical sense.&lt;/p&gt;

&lt;p&gt;Before reading the results, make a prediction: if A and B move perfectly together, does holding both reduce your risk?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Assumed correlation&lt;/th&gt;
&lt;th&gt;Portfolio volatility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-0.50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.00%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.14%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+0.25&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.81%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+0.80&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18.97%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.00%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At &lt;strong&gt;+1&lt;/strong&gt;, the portfolio is just as volatile as either constituent. Splitting your money across two names hasn't lowered this particular measure of risk.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;0&lt;/strong&gt;, volatility falls to about &lt;strong&gt;14.14%&lt;/strong&gt;, not 10%. Diversification helps, but risk doesn't shrink in a simple one-for-one proportion to the number of holdings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try the experiment on MarketDeck
&lt;/h3&gt;

&lt;p&gt;Open the &lt;a href="https://marketdeck.in/screener/learning/books/expected-returns/" rel="noopener noreferrer"&gt;Expected Returns interactive guide&lt;/a&gt; and find the exercise about adding holdings versus changing risks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set both assumed volatilities to &lt;strong&gt;20%&lt;/strong&gt; and the weights to &lt;strong&gt;50% / 50%&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set correlation to &lt;strong&gt;+1&lt;/strong&gt;. Predict the answer before running the experiment.&lt;/li&gt;
&lt;li&gt;Repeat with &lt;strong&gt;0&lt;/strong&gt; and &lt;strong&gt;-0.5&lt;/strong&gt;. Check your results against the table.&lt;/li&gt;
&lt;li&gt;Try changing only the weights to &lt;strong&gt;70% / 30%&lt;/strong&gt;. What happens, and why?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This exercise is an &lt;strong&gt;assumption-driven calculator&lt;/strong&gt;, not a backtest or a stock recommendation. That is its strength: you can isolate one idea instead of confusing it with changing market prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The mathematical reason
&lt;/h2&gt;

&lt;p&gt;For two assets, portfolio variance is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;portfolio_variance = wA² × σA² + wB² × σB² + 2 × wA × wB × σA × σB × correlation&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;w&lt;/code&gt; is the asset weight and &lt;code&gt;σ&lt;/code&gt; is the standard deviation of returns. Take the square root of the resulting variance to get portfolio volatility.&lt;/p&gt;

&lt;p&gt;Notice the final term. It connects the two assets. Ignoring that term amounts to ignoring how the stocks move together.&lt;/p&gt;

&lt;p&gt;For a larger portfolio, linear algebra gives us a cleaner expression:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;portfolio_variance = weights.T @ covariance_matrix @ weights&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;covariance matrix&lt;/strong&gt; contains individual asset variances along its diagonal and pairwise covariances elsewhere. The matrix is not just a technical convenience: its off-diagonal entries are part of the portfolio's risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The ten-stock illusion
&lt;/h2&gt;

&lt;p&gt;Now imagine &lt;strong&gt;ten hypothetical stocks&lt;/strong&gt;, each weighted at 10%, each with assumed annual volatility of 20%.&lt;/p&gt;

&lt;p&gt;To make the mechanism easy to see, let's assume every distinct pair has exactly the same correlation, which we'll call &lt;code&gt;rho&lt;/code&gt;. Real portfolios don't work this neatly. We're creating a controlled experiment, not modelling ten actual NSE-listed companies.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;n&lt;/code&gt; equal-weighted, equally volatile assets, the formula simplifies to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;portfolio_volatility = asset_volatility × sqrt((1 + (n - 1) × rho) / n)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Watch what happens as common correlation rises:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Correlation between every pair&lt;/th&gt;
&lt;th&gt;Portfolio volatility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.32%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.40%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.83%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17.61%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.00%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The portfolio always contains &lt;strong&gt;ten stocks&lt;/strong&gt;. Its weights and each stock's assumed volatility are unchanged. Only the relationships between them are different.&lt;/p&gt;

&lt;p&gt;That one change takes the estimated portfolio volatility from &lt;strong&gt;6.32% to 17.61%&lt;/strong&gt; when correlation goes from 0 to 0.75.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; Diversification is not only about owning different businesses. It's also about whether the risks driving those businesses are sufficiently different.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;An important constraint on negative correlations:&lt;/strong&gt; When every pair of assets shares the same correlation, the correlation matrix is only valid when −1/(n − 1) ≤ ρ ≤ 1.&lt;/p&gt;

&lt;p&gt;For ten assets, the minimum common correlation is approximately −0.111. So although −0.50 is a valid correlation between two assets, it cannot be the common correlation across all ten assets.&lt;/p&gt;

&lt;p&gt;The Python example checks the matrix's eigenvalues to reject invalid assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build the correlation lab in Python
&lt;/h2&gt;

&lt;p&gt;Let's reproduce these numbers with NumPy. This is standalone code: &lt;strong&gt;it does not use a MarketDeck API or download real stock prices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Install NumPy if needed with &lt;code&gt;pip install numpy&lt;/code&gt;, save the following as &lt;code&gt;portfolio_correlation.py&lt;/code&gt;, and run it with Python 3.&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;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="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;ASSET_VOL&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;# assumed annualised volatility
&lt;/span&gt;&lt;span class="n"&gt;weights&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;full&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;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;N&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;portfolio_lab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rho&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;# Correlation matrix: 1 on the diagonal, rho elsewhere
&lt;/span&gt;    &lt;span class="n"&gt;corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;&lt;span class="p"&gt;)&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;eye&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="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rho&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="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Reject invalid correlation matrices
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eigvalsh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corr&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1e-10&lt;/span&gt;

    &lt;span class="n"&gt;covariance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ASSET_VOL&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;corr&lt;/span&gt;
    &lt;span class="n"&gt;variance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;covariance&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;volatility&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Variance-equivalent count; equal individual volatilities
&lt;/span&gt;    &lt;span class="n"&gt;effective_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ASSET_VOL&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;variance&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;variance&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="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;volatility&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;effective_count&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;rho     volatility     effective_count&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;rho&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;portfolio_lab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rho&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="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;rho&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;4.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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="o"&gt;%&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;count&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;5.2&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="c1"&gt;# Two quick sanity checks
&lt;/span&gt;&lt;span class="k"&gt;assert&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;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portfolio_lab&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ASSET_VOL&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;sqrt&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="k"&gt;assert&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;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portfolio_lab&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ASSET_VOL&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;Sanity checks passed.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rho     volatility     effective_count
0.00        6.32%          10.00
0.25       11.40%           3.08
0.50       14.83%           1.82
0.75       17.61%           1.29
1.00       20.00%           1.00
Sanity checks passed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try modifying &lt;code&gt;N&lt;/code&gt;, the common correlation, or the weights. The volatility calculation works with unequal weights, and the revised effective-count calculation also accommodates unequal weights as long as the individual asset volatilities remain equal. It remains a variance-equivalent comparison, not a literal count of independent economic risks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What does “1.29 effective stocks” actually mean?
&lt;/h2&gt;

&lt;p&gt;At a common correlation of 0.75, the code prints &lt;strong&gt;1.29&lt;/strong&gt;. It sounds dramatic. It also needs a careful definition.&lt;/p&gt;

&lt;p&gt;Under our specific assumptions, we can ask: &lt;em&gt;How many equally weighted, equally volatile, mutually uncorrelated assets would produce the same portfolio variance?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That &lt;strong&gt;variance-equivalent number&lt;/strong&gt; is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;effective_count = n / (1 + (n - 1) × rho)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For ten assets with &lt;code&gt;rho = 0.75&lt;/code&gt;, it is about &lt;strong&gt;1.29&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; mean the portfolio contains literally 1.29 companies, that we have identified its true economic factors, or that its future losses can be predicted with precision. It is a comparison within an unusually simple model.&lt;/p&gt;

&lt;p&gt;Other measures, including eigenvalue-based estimates of the dimensionality of a correlation matrix, answer different questions and can produce different results. Don't combine them under the label "independent bets" without explaining which definition you used.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Where real portfolio research gets harder
&lt;/h2&gt;

&lt;p&gt;In a classroom example, we pick correlations. In a real portfolio, we must &lt;strong&gt;estimate&lt;/strong&gt; them from returns—and those estimates may be unstable.&lt;/p&gt;

&lt;p&gt;A few practical questions become essential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Which period?&lt;/strong&gt; Correlations based on the past three months may disagree with those based on three years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which returns?&lt;/strong&gt; Closing prices, total returns, currency conversions, and corporate actions must be handled consistently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which observations?&lt;/strong&gt; Holidays, missing prices, and securities with short histories can distort the matrix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which risks are missing?&lt;/strong&gt; Liquidity shocks, options payoffs, concentrated factors, and extreme market events may not be described well by ordinary correlation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What changed?&lt;/strong&gt; A historically diversified portfolio can become more correlated precisely when diversification matters most.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MarketDeck's &lt;a href="https://marketdeck.in/screener/portfolio-analysis/" rel="noopener noreferrer"&gt;Portfolio Analysis&lt;/a&gt; is designed for this broader research process. Its public overview describes connected lenses covering performance, core risk, tail risk, return distributions, factor exposures, diversification, risk–return analysis, and implied returns. &lt;strong&gt;Analysing your own holdings requires signing in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The two-asset &lt;a href="https://marketdeck.in/screener/learning/books/expected-returns/" rel="noopener noreferrer"&gt;learning lab&lt;/a&gt; is the place to understand the mechanics first. The portfolio workspace is where more of the real research questions can be considered together.&lt;/p&gt;

&lt;p&gt;Our goal isn't to turn covariance into a “buy” or “sell” score. It's to make the assumptions behind a result easier to inspect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try a rolling-correlation sensitivity check.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of estimating correlations once, repeat the calculation using rolling windows of 60 daily returns.&lt;/p&gt;

&lt;p&gt;With ten assets, each window contains 45 distinct pairwise correlation estimates. Short-window estimates can be noisy, so one effective-count figure may give a misleading impression of precision.&lt;/p&gt;

&lt;p&gt;Under the equal-weight, equal-volatility assumptions, an average pairwise correlation of 0.65 corresponds to approximately 1.46 variance-equivalent positions. At 0.85, the count drops to approximately 1.16.&lt;/p&gt;

&lt;p&gt;These are illustrative sensitivity calculations, not observed market results. They show why diversification estimates should be examined across different windows and assumptions rather than treated as permanent characteristics of a portfolio.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Three challenges before you close the notebook
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge 1 — Unequal risk:&lt;/strong&gt; Return to MarketDeck's two-asset experiment. Keep 50/50 weights and zero correlation, but give the second asset 30% volatility instead of 20%. Predict the new combined volatility. (&lt;em&gt;Check: approximately 18.03%.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge 2 — Concentrated weights:&lt;/strong&gt; In Python, change the ten weights so the first stock receives 50% and the remaining nine share the other 50%. Keep the common correlation at 0.75. Compare the result with the original 17.61%. (&lt;em&gt;Check: approximately 18.10%.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge 3 — Explain it simply:&lt;/strong&gt; A friend tells you, “I own ten stocks, so I'm diversified.” Ask two follow-up questions that would help establish whether that's actually true.&lt;/p&gt;

&lt;p&gt;You don't need to memorise every equation. If you remember one habit, make it this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't stop at “How many stocks do I own?” Ask “What makes these stocks move together, and how reliable is my estimate?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Explore and verify
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketdeck.in/screener/learning/books/expected-returns/" rel="noopener noreferrer"&gt;Run the MarketDeck correlation experiment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketdeck.in/screener/portfolio-analysis/" rel="noopener noreferrer"&gt;Explore MarketDeck Portfolio Analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketdeck.in/intelligence/" rel="noopener noreferrer"&gt;Browse MarketDeck's research and learning resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.linalg.eigvalsh.html" rel="noopener noreferrer"&gt;NumPy documentation: &lt;code&gt;numpy.linalg.eigvalsh&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1111/j.1540-6261.1952.tb01525.x" rel="noopener noreferrer"&gt;Harry Markowitz, &lt;em&gt;Portfolio Selection&lt;/em&gt; (1952)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;About MarketDeck:&lt;/em&gt; We're building an India-focused financial research workspace spanning screening, charts, portfolio analysis, and quantitative learning. I am part of the team behind MarketDeck. All numerical cases in this article are hypothetical educational examples, not historical backtests or personalised investment advice.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>finance</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
