<?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: sanjay mehta</title>
    <description>The latest articles on DEV Community by sanjay mehta (@sanjay_mehta_7618b5db60df).</description>
    <link>https://dev.to/sanjay_mehta_7618b5db60df</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%2F4175243%2Fb4dd4536-a12a-4bd0-a5aa-232c698f6242.png</url>
      <title>DEV Community: sanjay mehta</title>
      <link>https://dev.to/sanjay_mehta_7618b5db60df</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanjay_mehta_7618b5db60df"/>
    <language>en</language>
    <item>
      <title>I Almost Published a Chart Saying the Nifty Fell 51% in 2003. It Fell 16%. Here Is the Bug.</title>
      <dc:creator>sanjay mehta</dc:creator>
      <pubDate>Sun, 11 Oct 2026 07:41:33 +0000</pubDate>
      <link>https://dev.to/sanjay_mehta_7618b5db60df/i-almost-published-a-chart-saying-the-nifty-fell-51-in-2003-it-fell-16-here-is-the-bug-2dgn</link>
      <guid>https://dev.to/sanjay_mehta_7618b5db60df/i-almost-published-a-chart-saying-the-nifty-fell-51-in-2003-it-fell-16-here-is-the-bug-2dgn</guid>
      <description>&lt;p&gt;I keep a few million rows of Indian stock market data in PostgreSQL as a hobby. I started in 2025 because the free sites kept changing their page layouts and breaking the scripts I used to read them. I am a developer, not a quant, and I have spent a year quietly getting things wrong.&lt;/p&gt;

&lt;p&gt;Last week one of those mistakes got as far as a finished chart with a caption. The chart said the Nifty 50 fell 51 percent inside 2003. The true number is 16 percent. A reviewer caught it twenty minutes before I would have published it.&lt;/p&gt;

&lt;p&gt;That one is first. Three more follow, in the order I made them. Each has the hours it cost and the SQL I use now.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The 51 percent that was not there
&lt;/h2&gt;

&lt;p&gt;I wanted the deepest fall inside each calendar year, from a peak to the trough that followed. I wrote the obvious thing: lowest close divided by highest close, per year, minus one.&lt;/p&gt;

&lt;p&gt;For 2003 that gave about 51 percent. I believed it. The year finished up 71 percent, so a huge dip inside it made for a great caption: "even the best years have a crash in them."&lt;/p&gt;

&lt;p&gt;Except 2003 went up almost in a straight line. The low was in March and the high was in December. Low over high ignores which came first. It is just the year's range. The real peak-to-trough drop that year was 16 percent, and it happened in the spring, before the run.&lt;/p&gt;

&lt;p&gt;A drawdown has a direction. The peak must come before the trough. The way to encode that is a running maximum.&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;with&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;close&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;over&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="k"&gt;partition&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="k"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;year&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;
           &lt;span class="k"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;between&lt;/span&gt; &lt;span class="n"&gt;unbounded&lt;/span&gt; &lt;span class="k"&gt;preceding&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;current&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;
         &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;running_high&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;index_closes&lt;/span&gt;
  &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'NIFTY50'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;year&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;yr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;running_high&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="nb"&gt;numeric&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;max_drawdown_pct&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things in there bit me on the way to getting it right.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;partition by&lt;/code&gt; is not optional. My first "fixed" version left it out, so the running high for 2003 was the 2000 peak, and the answer came back as 47 percent. Still wrong, just differently. If you want the fall within a year, the high has to reset each year.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;::numeric&lt;/code&gt; cast is there because &lt;code&gt;round(double precision, integer)&lt;/code&gt; does not exist in PostgreSQL. My prices load as doubles. I have hit that error maybe fifteen times this year. I now cast before I round, every time, without thinking.&lt;/p&gt;

&lt;p&gt;Cost: about three hours across two evenings, plus the near miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The worst day that was really a worst exit
&lt;/h2&gt;

&lt;p&gt;Same table. I asked: for every possible buy day since 2005, what was the worst ten-year outcome?&lt;/p&gt;

&lt;p&gt;The answer was 23 March 2010. For three different indices. The same day.&lt;/p&gt;

&lt;p&gt;I spent forty minutes looking for a corrupt row. The closes around that date were smooth: 5,205 on the 22nd, 5,225 on the 23rd, 5,260 on the 25th. Nothing wrong.&lt;/p&gt;

&lt;p&gt;Then I checked the other end of the window. Ten years after 23 March 2010 is 23 March 2020, the bottom of the COVID crash. Buy days around 23 March 2010 all scored badly for the same reason. Their exits land in the same hole.&lt;/p&gt;

&lt;p&gt;The query was right. I was looking at the wrong date.&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="c1"&gt;-- print both ends, always&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;entry_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;entry_close&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;   &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt;  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exit_close&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;close&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="nb"&gt;numeric&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ret_pct&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt;   &lt;span class="n"&gt;index_closes&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="k"&gt;lateral&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;close&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;index_closes&lt;/span&gt;
  &lt;span class="k"&gt;where&lt;/span&gt;  &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'10 years'&lt;/span&gt;
  &lt;span class="k"&gt;order&lt;/span&gt;  &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;limit&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;exit&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt;  &lt;span class="n"&gt;entry&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="s1"&gt;'NIFTY50'&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt;  &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;ret_pct&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost: forty minutes, and a draft paragraph that called a correct result a data glitch, which I had to rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The company that became two companies
&lt;/h2&gt;

&lt;p&gt;I keyed everything on the ticker symbol. Tata Motors was &lt;code&gt;TATAMOTORS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In 2025 the company split into two listed entities. The symbol I had hard-coded changed, and a second one appeared for the commercial vehicle business. My join returned no rows for the old symbol after the split date. Because I was computing sector averages with a left join, the missing rows became nulls, and the average quietly dropped them. The sector average for autos moved and I did not notice for three weeks.&lt;/p&gt;

&lt;p&gt;Zomato renamed itself Eternal the same year. Smaller blast radius, same bug.&lt;/p&gt;

&lt;p&gt;The fix is the one every data person already knows and I had skipped because it was more tables: an internal integer id as the stable key, and a symbols table with &lt;code&gt;valid_from&lt;/code&gt; and &lt;code&gt;valid_to&lt;/code&gt;.&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;company&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="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;symbol_history&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;company_id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&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="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;valid_from&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;    &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;valid_to&lt;/span&gt;   &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;-- null = current&lt;/span&gt;
  &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;company_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- which company was 'TATAMOTORS' on 2024-06-01?&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt;   &lt;span class="n"&gt;symbol_history&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt;   &lt;span class="n"&gt;company&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&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;company_id&lt;/span&gt;
&lt;span class="k"&gt;where&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;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TATAMOTORS'&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt;    &lt;span class="s1"&gt;'2024-06-01'&lt;/span&gt; &lt;span class="k"&gt;between&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;valid_from&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;coalesce&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;valid_to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'9999-12-31'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost: the three weeks I did not notice, then one evening to backfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Zero meant three different things
&lt;/h2&gt;

&lt;p&gt;In my index table, a day could be absent (holiday), present with a close of zero (a padded gap from one loader), or present with a real close. My first forward-return query only checked that a row existed, so it happily computed returns against zeros and produced a few infinite percentages.&lt;/p&gt;

&lt;p&gt;I fixed it with &lt;code&gt;where close &amp;gt; 0&lt;/code&gt; and moved on. Then I reused the table for a volume query, where a zero on a real trading day is meaningful, and the same filter was wrong there.&lt;/p&gt;

&lt;p&gt;What I do now is dull. Holidays are not rows. Bad loads are nulls, never zeros. A zero is a zero. One evening to backfill, and I have not had this class of bug since.&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="c1"&gt;-- loader rule: never write 0 for "unknown"&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;index_closes&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;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;nullif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;double&lt;/span&gt; &lt;span class="nb"&gt;precision&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost: the two bugs took maybe an hour to find, plus the backfill evening. The second one was embarrassing because I had just "fixed" it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually do differently
&lt;/h2&gt;

&lt;p&gt;I do not have a grand rule. I have one habit I started the evening after the 2003 chart: before I put a single number in a caption, I look at the handful of rows that produced it. When I finally did that for the 51 percent, it took ten seconds. The date of the low came before the date of the high, and the caption fell apart.&lt;/p&gt;

&lt;p&gt;I did not have the habit when it mattered. A reviewer caught that one. The habit is my attempt to not need the reviewer next time. It would not have caught the symbol split, because no single row looked wrong, but it would have caught the other three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hobby scale. A few million rows, one machine. Some of these fixes cost more at real scale.&lt;/li&gt;
&lt;li&gt;Indian market data has its own oddities: lot sizes that change, expiry dates that get moved, holidays declared with a day's notice. Yours will have different ones.&lt;/li&gt;
&lt;li&gt;I found four so far.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a version of the 2003 mistake in your own domain, the kind where the number looks right and the dates say otherwise, I would like to hear it.&lt;/p&gt;

</description>
      <category>database</category>
      <category>debugging</category>
      <category>postgres</category>
      <category>sql</category>
    </item>
    <item>
      <title>I Gave an AI Agent 20 Years of Stock Index Data. The Hardest Part Wasn't the AI.</title>
      <dc:creator>sanjay mehta</dc:creator>
      <pubDate>Sun, 11 Oct 2026 03:05:58 +0000</pubDate>
      <link>https://dev.to/sanjay_mehta_7618b5db60df/i-gave-an-ai-agent-20-years-of-stock-index-data-the-hardest-part-wasnt-the-ai-4e7i</link>
      <guid>https://dev.to/sanjay_mehta_7618b5db60df/i-gave-an-ai-agent-20-years-of-stock-index-data-the-hardest-part-wasnt-the-ai-4e7i</guid>
      <description>&lt;p&gt;I had a question that is annoying to answer in a spreadsheet and trivial to say out loud: if you bought an Indian stock index on any random day since 2005 and held for N years, how often did you lose money?&lt;/p&gt;

&lt;p&gt;So I said it out loud to an AI assistant that had a Model Context Protocol connection to a server with daily index data back to 1996, and watched what it did. This post is about the plumbing, the one answer I was sure was wrong, and why that moment is the actual hard part of giving an agent real data.&lt;/p&gt;

&lt;p&gt;If you want the investing takeaway, I wrote that up &lt;a href="https://future-hacker.medium.com/i-checked-every-single-day-you-could-have-bought-nifty-since-2005-d05f1ee1d471" rel="noopener noreferrer"&gt;on Medium&lt;/a&gt;. This is the developer version.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built the MCP server used here. Details at the end.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Take every trading day from April 2005 to October 2016 as a buy day for
Nifty 50, Nifty Midcap 150 and Nifty Smallcap 250. For each buy day,
check the index level 1, 2, 3, 4, 5, 6, 7, 8 and 10 years later. Tell
me what share of buy days ended with the index lower. Also give the
median 5-year and 10-year growth rate, and the single worst 10-year
buy day.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No code in the prompt. No mention of which tools exist. The assistant reads the tool schemas the server exposes and decides.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually called
&lt;/h2&gt;

&lt;p&gt;Four tool calls, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Resolve the three index names&lt;/strong&gt; to the server's identifiers. Index names drift ("Nifty Midcap 150" has had other names), and the server has an as-of resolver for exactly this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch daily closes&lt;/strong&gt; for each index, 2005 to 2026. Three calls batched into one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch the observation calendar&lt;/strong&gt; so it knows which dates are trading days and which are holidays. This matters for "the first trading day N years later".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute&lt;/strong&gt; the forward-return table itself, in the model's own scratch space, then summarise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result, in about forty seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1-year holds that lost money:&lt;/strong&gt; Nifty 50 23.5%, Midcap 28.5%, Smallcap 34.2%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3-year:&lt;/strong&gt; 4.8%, 13.7%, 20.8%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5-year:&lt;/strong&gt; 1.7%, 2.3%, 15.2%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7-year and beyond:&lt;/strong&gt; 0%, 0%, 0%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Median 10-year growth:&lt;/strong&gt; 11.2%, 15.5%, 13.4% a year&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmf87y3yjzgzht6r3n4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmf87y3yjzgzht6r3n4a.png" alt="Share of buy days that lost money, by holding period, three Indian indices" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then recomputed the whole thing in SQL against the same table. Every percentage matched to one decimal. Good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I thought it went wrong, and why I was the one who was wrong
&lt;/h2&gt;

&lt;p&gt;I asked a follow-up: "What was the single worst 10-year buy day for each index?"&lt;/p&gt;

&lt;p&gt;It answered 23 March 2010. For all three indices. The same date.&lt;/p&gt;

&lt;p&gt;That looked like a data glitch to me. Three indices with different constituents, all bottoming out on the same Tuesday as the worst possible entry? I was sure a bad row was sitting in the table. I asked the agent to show me the closes around that date.&lt;/p&gt;

&lt;p&gt;They were smooth. 5,205 on the 22nd, 5,225 on the 23rd, 5,260 on the 25th. Nothing wrong with the buy day at all.&lt;/p&gt;

&lt;p&gt;Then I looked at the &lt;strong&gt;exit&lt;/strong&gt; date. Ten years after 23 March 2010 is 23 March 2020. That was the bottom of the COVID crash. The Nifty 50 closed at 7,610 that day, down 13 percent from the day before and 38 percent from its February high.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0wgk9xruwkelxeqyya9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0wgk9xruwkelxeqyya9.png" alt="Nifty 50 yearly returns 1996 to 2026 with the deepest fall inside each year" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the "worst buy day" was never about the buy day. It was about where the exit landed. Any buy day in March 2010 would have scored almost identically, because they all exit into the same hole. The agent's answer was correct. My instinct that it was broken was the error.&lt;/p&gt;

&lt;p&gt;Here is what bothers me about that. The agent gave me a true number with no explanation attached. A human analyst would have said "23 March 2010, but only because the exit falls on the COVID low". The agent said "23 March 2010" and stopped. I nearly threw away a correct answer because it arrived without its reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would change in the tool, not the model
&lt;/h2&gt;

&lt;p&gt;The fix is not a smarter model. It is a tool description that asks for the reason to travel with the number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_price_history"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Daily or weekly closes. When a caller derives a single extreme from this data (worst day, best day, peak, trough), report BOTH the entry and exit dates and check each against get_observation_status so a crash day or holiday boundary is named, not hidden."&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;One sentence in a schema. Agents follow instructions in tool descriptions more reliably than instructions in prompts, because the description is present on every call and the prompt is not.&lt;/p&gt;

&lt;p&gt;I have not shipped that change yet. I am writing it here first because I want to see whether other people have hit the same shape of problem: a correct answer that looks wrong because the context that makes it correct was never requested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three rules I now follow when exposing data to agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Every extreme comes with both dates.&lt;/strong&gt; A "worst 10-year window" is two dates, not one. If the tool only returns the entry, the caller will blame the entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Dates get a status, not just a value.&lt;/strong&gt; Holiday, weekend, pre-listing, post-delisting, crash day. The server already exposes this as an observation-status tool. I did not ask for it, so the agent did not call it. The schema should tell it when to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Identifiers are time-dependent.&lt;/strong&gt; The index called "Nifty Midcap 150" today was not called that in 2005. Tata Motors became two companies in 2025. A tool that resolves names as-of a date is the difference between a right answer and a confident wrong one.&lt;/p&gt;

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

&lt;p&gt;Four tool calls for the main question, two for the follow-up, two more to look at the closes around the suspicious date. Eight calls total against a free tier of fifty a day. The SQL I wrote to verify it took me longer than the agent took to produce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Price-only index data. Dividends excluded, so real returns are better than the numbers above.&lt;/li&gt;
&lt;li&gt;One agent, one model family, one afternoon. Your tool descriptions may need different wording for different models.&lt;/li&gt;
&lt;li&gt;The "zero losers at seven years" result is about Indian indices from 2005 to 2026. It is a fact about the past.&lt;/li&gt;
&lt;li&gt;I built the server, so I knew the status tool existed and could check the exit date in seconds. A user who did not might have shipped "the data is broken" instead of "the exit landed on COVID".&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;What is the equivalent of "check outliers first" in your domain, and have you put it in a schema yet? I would genuinely like to know what others are writing into tool descriptions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The server is at &lt;a href="https://tapetide.com/mcp" rel="noopener noreferrer"&gt;tapetide.com/mcp&lt;/a&gt;: about 8,200 NSE and BSE stocks plus indices, MCP, free for 50 calls a day. Not investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>india</category>
    </item>
    <item>
      <title>Foreign Investors Have Sold Indian Stocks for 6 Years Straight. I Asked an AI to Show Me Who Is Buying.</title>
      <dc:creator>sanjay mehta</dc:creator>
      <pubDate>Sat, 10 Oct 2026 12:41:47 +0000</pubDate>
      <link>https://dev.to/sanjay_mehta_7618b5db60df/foreign-investors-have-sold-indian-stocks-for-6-years-straight-i-asked-an-ai-to-show-me-who-is-3pmo</link>
      <guid>https://dev.to/sanjay_mehta_7618b5db60df/foreign-investors-have-sold-indian-stocks-for-6-years-straight-i-asked-an-ai-to-show-me-who-is-3pmo</guid>
      <description>&lt;p&gt;Every few weeks a headline says foreign investors are "dumping" Indian stocks. Every few weeks the Nifty refuses to collapse. I wanted to know why, and I wanted the answer from the data, not from a TV panel.&lt;/p&gt;

&lt;p&gt;So I asked an AI assistant. Not a plain chatbot (I tested that separately, and &lt;a href="https://future-hacker.medium.com/i-asked-ai-30-simple-questions-about-indian-stocks-it-got-18-wrong-30ea8a752e10" rel="noopener noreferrer"&gt;it was wrong 18 times out of 30&lt;/a&gt;). This one had a Model Context Protocol connection to a server with daily institutional flow data back to 2014, index valuations back to 2016, and current filings for about 8,200 NSE and BSE companies.&lt;/p&gt;

&lt;p&gt;Here is the prompt, the three charts it produced, and the one number I did not expect.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice. Figures are from exchange disclosures as of 9 October 2026. I built the data server used here; details at the end.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show yearly net FII and DII flows in the Indian cash market since 2014,
then monthly since January 2024. How many consecutive months have
domestic institutions been net buyers? And where does today's Nifty 50
P/E sit against every trading day since 2016?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole input. Four tool calls happened behind it: one for the daily flow history, one for the monthly roll-up, one for the index valuation history, one for the latest close.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chart 1: the yearly picture
&lt;/h2&gt;

&lt;p&gt;Foreign institutions (FIIs) have been net sellers of Indian equities in every calendar year since 2021. Not most years. Every year.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2021:&lt;/strong&gt; FII −₹0.9 lakh crore, DII +₹0.9 lakh crore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2022:&lt;/strong&gt; FII −₹2.8 lakh crore, DII +₹2.8 lakh crore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2023:&lt;/strong&gt; FII −₹0.2 lakh crore, DII +₹1.8 lakh crore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2024:&lt;/strong&gt; FII −₹3.0 lakh crore, DII +₹5.3 lakh crore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2025:&lt;/strong&gt; FII −₹3.1 lakh crore, DII +₹7.9 lakh crore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2026 to 9 Oct:&lt;/strong&gt; FII −₹4.4 lakh crore, DII +₹6.8 lakh crore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 2026 foreign outflow is already larger than any full year on record, with a quarter of the year left. Domestic institutions (DIIs, which is mostly your SIP money flowing through mutual funds, plus insurers and pension funds) have absorbed all of it and then some.&lt;/p&gt;

&lt;p&gt;One lakh crore is roughly 12 billion US dollars, if that helps calibrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chart 2: month by month
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjh5w15022id1m6cdxm61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjh5w15022id1m6cdxm61.png" alt="Monthly net FII and DII flows, January 2024 to October 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thirty-four months from January 2024 to October 2026. Foreign money left in 26 of them. Domestic money arrived in all 34.&lt;/p&gt;

&lt;p&gt;The tallest month was March 2026: FIIs sold ₹1.23 lakh crore, DIIs bought ₹1.43 lakh crore. In a single month.&lt;/p&gt;

&lt;p&gt;The number I asked for and did not expect: counted back from today, domestic institutions have been net buyers for &lt;strong&gt;39 consecutive months&lt;/strong&gt;. The last month they were net sellers was mid-2023.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chart 3 is a sentence, not a picture
&lt;/h2&gt;

&lt;p&gt;The Nifty 50 closed at 22,520 on 9 October 2026, down about 14 percent from its end-2025 close of 26,130. Its trailing P/E was 19.3.&lt;/p&gt;

&lt;p&gt;Across 2,659 trading days since January 2016, the index has been cheaper than that on &lt;strong&gt;0.9 percent&lt;/strong&gt; of them. The median P/E over the period is 23.2. The 2021 peak was 42.&lt;/p&gt;

&lt;p&gt;So the market is down, foreigners are selling at a record pace, domestic investors are buying at a record pace, and the index is at a valuation it has visited on roughly 24 trading days in a decade. I am not telling you what that means. I am telling you it took one prompt to see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this needed MCP and not a chatbot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnybb02rum4wvygnk3gbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnybb02rum4wvygnk3gbt.png" alt="A chatbot's remembered figures for Indian stocks vs actual filings" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran the same question past the same model with no data connection first. It gave me a confident paragraph about "FII selling pressure in 2024" and a Nifty P/E of "around 22". It had no idea about 2025 or 2026 because it has no memory past its training cutoff, and it had no way to count a 39-month streak because it had never seen the months.&lt;/p&gt;

&lt;p&gt;MCP is an open standard (Anthropic released it, it now lives under the Linux Foundation) that lets an assistant call outside tools mid-conversation. Connect a data server and the model stops remembering and starts fetching. Every number comes back with the date it was observed. You can ask it to show the rows behind any conclusion.&lt;/p&gt;

&lt;p&gt;For developers, the interesting part is how little glue this takes. There is no API client to write. The assistant reads the tool schemas, picks the four it needs, and chains them. Here is the same workflow from Cursor or VS Code, which is one config block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"indian-markets"&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.tapetide.com/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First use opens a browser for Google sign-in. After that the assistant can call the flow, valuation, screening, financials and filing tools directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to watch out for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data freshness varies by dataset.&lt;/strong&gt; Daily flows are end-of-day. Shareholding follows quarterly filings. Read the dates on every answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model can still do arithmetic wrong.&lt;/strong&gt; The data arrives correct; the summing is the model's. Ask for the underlying rows when a total matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"DII" is a bucket, not a person.&lt;/strong&gt; It includes mutual funds, insurers, pension funds and banks. The streak tells you domestic institutions as a group kept buying. It does not tell you retail investors are right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A free tier of 50 tool calls a day is plenty for questions like this one&lt;/strong&gt; and not enough for an agent that loops. Be specific.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Pick any question you have been answering by opening six browser tabs. Promoter pledge trends. FII holding changes across a sector. Which indices led last month. Ask it in one sentence, then ask for the rows.&lt;/p&gt;

&lt;p&gt;The data server used here is one I built. It covers about 8,200 NSE and BSE stocks, speaks MCP, and is free for 50 calls a day. Setup guides for Claude, ChatGPT, Cursor and the CLIs are at &lt;a href="https://tapetide.com/mcp" rel="noopener noreferrer"&gt;tapetide.com/mcp&lt;/a&gt;. The companion piece on how badly a chatbot does without it is &lt;a href="https://future-hacker.medium.com/i-asked-ai-30-simple-questions-about-indian-stocks-it-got-18-wrong-30ea8a752e10" rel="noopener noreferrer"&gt;on Medium&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not investment advice. Check the filings before you act on anything.&lt;/p&gt;

</description>
      <category>finance</category>
      <category>mcp</category>
      <category>india</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
