<?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: oji - building AI in public</title>
    <description>The latest articles on DEV Community by oji - building AI in public (@masaoshimadaopen).</description>
    <link>https://dev.to/masaoshimadaopen</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%2F4013207%2F62889ff7-f41e-4077-9836-3fafe971b8ce.jpg</url>
      <title>DEV Community: oji - building AI in public</title>
      <link>https://dev.to/masaoshimadaopen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/masaoshimadaopen"/>
    <language>en</language>
    <item>
      <title>My AI Agent Recommended a Non-Existent Investment Product — Exposing Information Gaps Between Fund Distributors and Asset Manage</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Tue, 25 Aug 2026 23:30:20 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-ai-agent-recommended-a-non-existent-investment-product-exposing-information-gaps-between-fund-12j5</link>
      <guid>https://dev.to/masaoshimadaopen/my-ai-agent-recommended-a-non-existent-investment-product-exposing-information-gaps-between-fund-12j5</guid>
      <description>&lt;p&gt;Hey everyone, it's your average 38-year-old developer here, hacking away at AI agents and automated trading bots in my evenings and weekends, just like many of you. &lt;/p&gt;

&lt;p&gt;Today, I want to share a recent blunder with a financial information AI agent I built. What started as a chuckle-worthy mistake turned into a serious lesson about "information reliability" — a critical factor that can be fatal for any automated system design.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: AI Recommends a "Non-Existent Investment Trust"
&lt;/h3&gt;

&lt;p&gt;With the new NISA (Japan's tax-advantaged investment program) kicking off, I was building an AI agent to list investment trusts tracking specific indices, primarily to rebalance my own portfolio. I started by asking it to compare products linked to the trending "FANG+" index.&lt;/p&gt;

&lt;p&gt;Within seconds of my prompt, the AI confidently replied:&lt;/p&gt;

&lt;p&gt;"For FANG+ linked investment trusts, considering trust fees and performance, I recommend the following two:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;eMAXIS Slim FANG+&lt;/strong&gt;: This series aims for the lowest operational costs in the industry and is highly recommended.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;iFreeNEXT FANG+&lt;/strong&gt;: It has a strong track record and offers stable management."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hmm, interesting. I knew about iFreeNEXT FANG+, but "eMAXIS Slim FANG+"?&lt;/p&gt;

&lt;p&gt;"Wait, is that even a thing?"&lt;/p&gt;

&lt;p&gt;I know the eMAXIS Slim series is popular for things like S&amp;amp;P500 and global index funds, but FANG+? I'd never heard of it. Skeptical, I quickly Googled it.&lt;/p&gt;

&lt;p&gt;...And indeed, it didn't exist.&lt;/p&gt;

&lt;p&gt;The AI had confidently fabricated a plausible-sounding product and recommended it. This was a red flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deeper Dive: The Trap of Information Hierarchy
&lt;/h3&gt;

&lt;p&gt;AI "hallucinations" are common. It likely combined the popular "eMAXIS Slim" brand with the popular "FANG+" index to generate a seemingly plausible answer. &lt;/p&gt;

&lt;p&gt;It would be easy to just dismiss AI as "unreliable" and move on. But as an engineer, I wanted to dig deeper. What if the agent had only returned real product names? Would I have blindly trusted it and moved on to the next step?&lt;/p&gt;

&lt;p&gt;Curious, I decided to cross-reference multiple sources for the legitimate "iFreeNEXT FANG+". Specifically, I checked the website of "Company A" (a distributor selling the product) and the official website of "Daiwa Asset Management" (the asset manager that operates the fund).&lt;/p&gt;

&lt;p&gt;What I found was even more concerning:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The NISA growth investment category eligibility status conflicted between the two sources.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Asset Manager's (Daiwa Asset) Official Site&lt;/strong&gt;: Clearly stated as eligible.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Distributor's (Company A) Site&lt;/strong&gt;: Seemed to indicate it was ineligible, or the information was outdated and not updated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which one is correct? Unsurprisingly, it's the "asset manager" who creates the product. The distributor is essentially a "retailer" that procures and sells the product. They might have delayed updates or simple transcription errors.&lt;/p&gt;

&lt;p&gt;This incident made me realize that information has a clear "hierarchy":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Primary Information&lt;/strong&gt;: The source of the information, like the asset manager's official website.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Secondary Information&lt;/strong&gt;: Information that processes or reposts primary information, such as distributor websites, news articles, or blogs.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;AI Generation&lt;/strong&gt;: Information learned, re-synthesized, and generated by AI from these sources.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My agent had completely ignored this hierarchy. It treated all information gathered from the internet as flat data, simply summarized by the AI. This inherent risk meant it could recommend non-existent products or be misled by outdated secondary data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Embedding Fact-Checking into Code
&lt;/h3&gt;

&lt;p&gt;This incident fundamentally changed my approach to designing information gathering agents. It's not enough to just have AI "research"; you need to build in mechanisms to "verify" for it to be practical.&lt;/p&gt;

&lt;p&gt;The solution was simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Designate Reliable Sources as "Truth"&lt;/strong&gt;: In this case, data obtained from the "asset manager's official website" is defined as the master data (primary information).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Automate Cross-Checking&lt;/strong&gt;: Any data obtained from AI or other secondary sources &lt;em&gt;must&lt;/em&gt; be cross-referenced against the master data for fact-checking.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Specifically, I implemented logic like this using Python (pandas):&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="c1"&gt;# Primary data obtained from the asset manager's official website (master data)
&lt;/span&gt;&lt;span class="n"&gt;primary_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fund Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;iFreeNEXT FANG+&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;iFreeNEXT NASDAQ100&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;Asset Manager&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Daiwa Asset Management&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;Daiwa Asset Management&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;NISA Growth Eligible&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="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Source&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Asset Manager Official&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;Asset Manager Official&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;df_primary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# AI's recommendation list (including the fabricated product)
&lt;/span&gt;&lt;span class="n"&gt;ai_recommendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fund Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;eMAXIS Slim FANG+&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;iFreeNEXT FANG+&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;Reason&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Lowest trust fees&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;Strong track record&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;df_ai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai_recommendation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# --- Fact-checking logic ---
# 1. Check if AI's recommendation exists in primary data (master)
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fund_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;df_ai&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fund Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fund_name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;df_primary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fund Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&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;[WARNING] AI recommended &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fund_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; which does not exist in master data.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Merge distributor information with primary information to detect discrepancies
# (Skipped here, but involves merging distributor data with df_primary and checking for differences)
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code doesn't blindly trust the AI's output. It first checks if the fund names recommended by the AI exist in our defined &lt;code&gt;df_primary&lt;/code&gt; (primary information) list. Anything not found is flagged as a "warning".&lt;/p&gt;

&lt;p&gt;Furthermore, by merging specifications from secondary sources (like distributor sites) with the primary information, we can automatically detect issues like the "NISA eligibility discrepancy."&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lesson: Master Your Primary Sources
&lt;/h3&gt;

&lt;p&gt;What I learned from this failure is that AI is a highly capable assistant, but not the ultimate decision-maker. Especially in domains requiring accuracy, such as finance or technical information, a system to verify AI output is the lifeline.&lt;/p&gt;

&lt;p&gt;And the foundation of that verification is the ability to discern "which information is primary."&lt;/p&gt;

&lt;p&gt;When building automated systems, it's easy to gravitate towards readily accessible secondary information or APIs. But we must constantly ask: where did this information come from? Is its "freshness" and "reliability" guaranteed?&lt;/p&gt;

&lt;p&gt;Ultimately, the biggest leverage comes from automating the tedious process of verification. Even if the AI lies, the overall system can still produce correct outputs. This incident reaffirmed my commitment to building robust systems that can handle such challenges. ✍️&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>When I Asked AI to Analyze "FANG+" and It Started Investigating Oil Companies — The Ticker Symbol Trap</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Tue, 25 Aug 2026 03:26:48 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/when-i-asked-ai-to-analyze-fang-and-it-started-investigating-oil-companies-the-ticker-symbol-2la7</link>
      <guid>https://dev.to/masaoshimadaopen/when-i-asked-ai-to-analyze-fang-and-it-started-investigating-oil-companies-the-ticker-symbol-2la7</guid>
      <description>&lt;p&gt;Hey there, it's your friendly neighborhood old guy. By day, I'm doing my regular gig, but by night, I'm tinkering with AI agents and algo-trading bots – I'm 38, for context.&lt;/p&gt;

&lt;p&gt;Last weekend, while trying to integrate a new strategy into my investment analysis bot, I stumbled into a rather amusing (and frankly, a bit dangerous) pitfall. Consider this a self-admonition, meticulously documented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asked for FANG+ Analysis, Got an Oil Price Report
&lt;/h3&gt;

&lt;p&gt;The genesis of the problem was simple. I was curious about recent tech stock movements, so I instructed my custom AI agent: "Analyze the FANG+ index and report on its future outlook."&lt;/p&gt;

&lt;p&gt;This agent is designed to gather relevant data based on a given theme, analyze it, and generate a summary. As usual, I kicked off the task, and it started processing immediately.&lt;/p&gt;

&lt;p&gt;Minutes later, I looked at the generated report and scratched my head.&lt;/p&gt;

&lt;p&gt;"...Something's off, isn't it?"&lt;/p&gt;

&lt;p&gt;The report indeed stated "FANG analysis." But the content had absolutely no mention of tech companies. Instead, it was all about the energy sector: crude oil price trends, shale oil extraction costs, OPEC production volumes.&lt;/p&gt;

&lt;p&gt;It felt like reading an earnings call transcript for an oil company.&lt;/p&gt;

&lt;p&gt;"Wait, why? Did I mess up the prompt?"&lt;/p&gt;

&lt;p&gt;No, I double-checked the logs; the instruction was clearly "Analyze FANG+." Did the AI bug out? I wondered, but tracing its thought process revealed that the AI itself had acted with extreme logic. The problem lay in the "environment" I had provided.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Culprit: "FANG" Lurking in the S&amp;amp;P 500 List
&lt;/h3&gt;

&lt;p&gt;I quickly pinpointed the cause.&lt;/p&gt;

&lt;p&gt;When this AI agent searches for analysis targets, it refers to an investment universe (a list of target stocks) that I've prepped. In this case, I had fed it an S&amp;amp;P 500 constituent list I had on hand, verbatim.&lt;/p&gt;

&lt;p&gt;Here’s an excerpt from that list:&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="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;VRSK&lt;/span&gt; &lt;span class="n"&gt;XEL&lt;/span&gt; &lt;span class="n"&gt;CTSH&lt;/span&gt; &lt;span class="n"&gt;TTWO&lt;/span&gt; &lt;span class="n"&gt;LULU&lt;/span&gt; &lt;span class="n"&gt;FANG&lt;/span&gt; &lt;span class="n"&gt;CEG&lt;/span&gt; &lt;span class="n"&gt;TEAM&lt;/span&gt; &lt;span class="n"&gt;AZN&lt;/span&gt; &lt;span class="n"&gt;ZS&lt;/span&gt; &lt;span class="n"&gt;DXCM&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably see it now.&lt;/p&gt;

&lt;p&gt;When the AI received the instruction "FANG+", it found the string "FANG" within this list. It then interpreted this as "This must be the FANG the user is referring to," and commenced its analysis.&lt;/p&gt;

&lt;p&gt;This ticker symbol, &lt;code&gt;FANG&lt;/code&gt;, has, of course, absolutely nothing to do with the tech index. It's the ticker for "Diamondback Energy, Inc.," an oil and natural gas company based in Texas.&lt;/p&gt;

&lt;p&gt;No wonder I got a report on crude oil prices. The AI wasn't wrong. If anything, it made the most "logical" decision given the dataset. This misunderstanding, frankly, was wild. A human would infer "contextually, they mean the tech index," but AI purely deals with data. Its purity, in this case, backfired.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Eliminate Ambiguity, Define Universe Strictly
&lt;/h3&gt;

&lt;p&gt;To prevent these kinds of incidents, there's only one way: rigorously eliminate ambiguity from the instructions and data given to the AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Be Specific with Prompts&lt;/strong&gt;&lt;br&gt;
A vague instruction like "Analyze FANG+" was the problem. If I had specified the ticker symbol, such as "Analyze the NYSE FANG+ Index (^NYFANG)", there would have been no room for the AI to err.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Separate Data Universes&lt;/strong&gt;&lt;br&gt;
Fundamentally, treating individual stocks (Stock) and indices (Index) or ETFs within the same list was a mistake.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data source for stocks&lt;/li&gt;
&lt;li&gt;Data source for ETFs&lt;/li&gt;
&lt;li&gt;Data source for indices
These should have been clearly separated, and the AI agent's search scope should have been limited. For example, if the task is "index analysis," it should be constrained to only refer to the index list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, I've revised the prompt template and added a UI where the user (me) explicitly specifies the type of analysis target (stock, ETF, index). This should significantly reduce the risk of ticker symbol collisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lesson: Financial Data is a Minefield of "Name Collisions"
&lt;/h3&gt;

&lt;p&gt;The lesson from this failure is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the world of financial data, similar strings frequently mean entirely different things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just as "FANG" is a colloquial term for a tech index and simultaneously the ticker for an oil company, these kinds of "name collisions" are everywhere. For example, &lt;code&gt;AMZN&lt;/code&gt; is Amazon, but &lt;code&gt;AMZ&lt;/code&gt; might be a JPMorgan index-linked security.&lt;/p&gt;

&lt;p&gt;You cannot expect AI to perform the "contextual reading" that humans do unconsciously. When developing AI agents, the "Garbage In, Garbage Out" principle is absolute. No matter how advanced the LLM, if the reference data sources or underlying assumptions are flawed, the output will be unreliable.&lt;/p&gt;

&lt;p&gt;Squashing these mundane bugs one by one, that's the reality of solo dev work, I think. More than flashy new features, this foundational data hygiene is far more crucial for a bot's stable operation.&lt;/p&gt;

&lt;p&gt;Now, which bug to squash next? I'll report back if I mess up again. 👍&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Predicting Shareholder Meeting Failure: How AI Quantified Risk from a Single Sentence in Past Minutes and a 5x Increase in Share</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Mon, 24 Aug 2026 00:28:11 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/predicting-shareholder-meeting-failure-how-ai-quantified-risk-from-a-single-sentence-in-past-4265</link>
      <guid>https://dev.to/masaoshimadaopen/predicting-shareholder-meeting-failure-how-ai-quantified-risk-from-a-single-sentence-in-past-4265</guid>
      <description>&lt;p&gt;Hey, it's your friendly neighborhood dev-grandpa here, still chugging along building AI agents on weeknights and weekends.&lt;/p&gt;

&lt;p&gt;Today, I want to share a story about how one of my custom analysis bots dug up a significant risk from a completely unexpected angle. I'll walk through how the AI quantitatively assessed the probability of a seemingly unpredictable event—whether a shareholder meeting would successfully pass a critical resolution—by analyzing past meeting minutes and other disclosure documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trigger: Will This Reverse Stock Split Actually Pass?
&lt;/h3&gt;

&lt;p&gt;I had a particular U.S. company under monitoring by my bot. This company was planning a reverse stock split, a common move to boost share price, usually approved without issue at shareholder meetings.&lt;/p&gt;

&lt;p&gt;But something felt off. This company had become incredibly popular with retail investors, and its shareholder base was growing rapidly. I started to wonder: if the shareholders were too dispersed, would it be difficult to reach the required quorum for the meeting to proceed?&lt;/p&gt;

&lt;p&gt;To test this hypothesis, I instructed my AI agent: "Read all of this company's past SEC filings (like EDINET in Japan) and identify any risks related to shareholder meetings."&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI's Discovery: A Single Sentence Revealing Past Failures
&lt;/h3&gt;

&lt;p&gt;The agent accessed the EDGAR database and started downloading years' worth of proxy statements (DEF 14A/C). Hundreds of megabytes. A human would spend days just reading through it.&lt;/p&gt;

&lt;p&gt;Using NLP, it began parsing the text, extracting relevant passages based on keywords suggesting meeting delays or failures, like "quorum," "adjourn," and "postpone."&lt;/p&gt;

&lt;p&gt;A few minutes later, the agent pinged me on Slack:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Found a history of the Special Meeting being adjourned twice due to a lack of a quorum several years ago."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No way. Seriously?&lt;/p&gt;

&lt;p&gt;The discovery was a single sentence buried in the minutes from that time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;... the Special Meeting was adjourned on two occasions due to a lack of a quorum. At the reconvened meeting, approximately 45.9% of the outstanding shares were present...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In essence: "The meeting was postponed twice because they couldn't get a quorum. Even when reconvened, only 45.9% of shares were present." That's below the majority. This is a big deal; usually, this doesn't happen.&lt;/p&gt;

&lt;p&gt;This qualitative information – the fact that they'd screwed up before – was already a significant red flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combining Qualitative Insights with Quantitative Data
&lt;/h3&gt;

&lt;p&gt;But the AI's job wasn't done. The mere fact that they "failed in the past" is too crude for proper analysis. The crucial next step was to compare: &lt;strong&gt;"How different are the conditions now compared to then?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, I gave the agent the next set of tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Retrieve the number of outstanding shares at the time of the quorum failure.&lt;/li&gt;
&lt;li&gt; Retrieve the current number of outstanding shares.&lt;/li&gt;
&lt;li&gt; Compare the two to estimate the degree of shareholder dispersion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The results came back quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Outstanding shares then: approx. 180 million&lt;/li&gt;
&lt;li&gt;  Outstanding shares now: approx. 950 million&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Astoundingly, since the failed shareholder meeting, the number of outstanding shares had ballooned by &lt;strong&gt;5.1 times&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is where the dots finally connected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Qualitative (Past Information):&lt;/strong&gt; This company inherently struggles with proxy solicitation for shareholder meetings.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Quantitative (Current Data):&lt;/strong&gt; The number of shareholders has increased by 5.1 times since then, making further dispersion extremely likely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining these two pieces, I could form a highly accurate hypothesis: "The risk of this upcoming shareholder meeting failing due to lack of a quorum is considerably high."&lt;/p&gt;

&lt;p&gt;In code, the logic looks something like this:&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;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_governance_risks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extracts governance risks from disclosure document text&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;risks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="c1"&gt;# History of adjournment due to lack of quorum
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;adjourned .* due to a lack of a quorum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;risks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;QUORUM_FAILURE_HISTORY&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="bp"&gt;True&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract attendance rate
&lt;/span&gt;    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(\d+\.\d+)% of the outstanding shares were present&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_text&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;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;attendance_rate&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;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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;if&lt;/span&gt; &lt;span class="n"&gt;attendance_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;risks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOW_ATTENDANCE_RATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attendance_rate&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;risks&lt;/span&gt;

&lt;span class="c1"&gt;# --- Evaluation Logic ---
# Past document text (sample)
&lt;/span&gt;&lt;span class="n"&gt;past_document_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;...&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Special&lt;/span&gt; &lt;span class="n"&gt;Meeting&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;adjourned&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="n"&gt;occasions&lt;/span&gt; &lt;span class="n"&gt;due&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;lack&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;quorum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;At&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;reconvened&lt;/span&gt; &lt;span class="n"&gt;meeting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;approximately&lt;/span&gt; &lt;span class="mf"&gt;45.9&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;outstanding&lt;/span&gt; &lt;span class="n"&gt;shares&lt;/span&gt; &lt;span class="n"&gt;were&lt;/span&gt; &lt;span class="n"&gt;present&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# Past and current outstanding shares (in millions)
&lt;/span&gt;&lt;span class="n"&gt;shares_past&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;186.8&lt;/span&gt;
&lt;span class="n"&gt;shares_current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;949.7&lt;/span&gt;

&lt;span class="n"&gt;risks_found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_governance_risks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;past_document_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;share_increase_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shares_current&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;shares_past&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;risks_found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;QUORUM_FAILURE_HISTORY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;share_increase_factor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&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;High Risk: History of quorum issues AND shareholder base dispersed by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;share_increase_factor&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x.&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;h3&gt;
  
  
  The Final Piece: Reading the Company's Countermeasures
&lt;/h3&gt;

&lt;p&gt;However, it's still too early to conclude "high risk!" The company must have learned from its past mistakes.&lt;/p&gt;

&lt;p&gt;I had the agent re-read the latest proxy statement in detail. Sure enough, they had taken action.&lt;/p&gt;

&lt;p&gt;Among the proposals for the current meeting was an agenda item to "amend the Bylaws to reduce the quorum for shareholder meetings from a majority to one-third."&lt;/p&gt;

&lt;p&gt;Smart move. They were lowering the bar themselves.&lt;/p&gt;

&lt;p&gt;This significantly offsets the risk of a quorum failure. Still, the facts of past failures and the rapid increase in shareholders remain. The final assessment landed somewhere around: "Risk still exists, but it's controlled to a non-fatal level."&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned: Combining Text and Numbers Changes Everything
&lt;/h3&gt;

&lt;p&gt;This whole experience offered significant lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Combining qualitative information (text) with quantitative data (numbers) explodes the resolution of analysis.&lt;/strong&gt; AI agents excel at processing both at high speed.&lt;/li&gt;
&lt;li&gt;  Even for seemingly unpredictable events, digging into past logs (meeting minutes, error logs, anything) allows for some degree of quantitative risk prediction for the future.&lt;/li&gt;
&lt;li&gt;  Ignoring countermeasures or changes made by the other party (company, system, market) leads to incomplete analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just about automated trading. It applies to predicting failures in your own services, measuring the effectiveness of marketing campaigns, and many other scenarios. &lt;/p&gt;

&lt;p&gt;Ultimately, AI isn't a magic wand. It's a tool for automating the grunt work of diligent data collection and then interpreting that data to form hypotheses. But because of it, you can sometimes uncover insights that a human would never find. That's why this side hustle is so engaging.&lt;/p&gt;

&lt;p&gt;Alright, which bot should I tinker with next? I'll share if I stumble upon any more interesting failure logs. See ya.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>My AI Lied to Me About a Stock Crash — Adversarial Testing Revealed Its Limits (and How to Use AI Correctly)</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Sat, 22 Aug 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-ai-lied-to-me-about-a-stock-crash-adversarial-testing-revealed-its-limits-and-how-to-use-ai-31al</link>
      <guid>https://dev.to/masaoshimadaopen/my-ai-lied-to-me-about-a-stock-crash-adversarial-testing-revealed-its-limits-and-how-to-use-ai-31al</guid>
      <description>&lt;p&gt;Hey, it's OJ. I'm 38 and dabble in AI agents and algorithmic trading bots as a side gig.&lt;/p&gt;

&lt;p&gt;Recently, I had a pretty "brutal" experience: an AI agent I built straight-up lied to me. As part of my build-in-public journey, I felt this was a crucial lesson to share, especially for anyone trying to automate information gathering with AI. So, here's the log.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Trigger: A Stock I Held Dropped -33%
&lt;/h3&gt;

&lt;p&gt;One day, a stock I was watching plummeted -33%. Normally, I'd immediately jump to official disclosure sites or the company's IR page, frantically searching for timely disclosures.&lt;/p&gt;

&lt;p&gt;But this time, I saw it as a perfect opportunity to test a research AI agent I was developing. Its supposed job: give it a company name and a time period, and it would collect related news and disclosures from the web, then analyze and report on stock price movement factors.&lt;/p&gt;

&lt;p&gt;So, I threw it a prompt: "Report the reason for XX company's sudden stock drop, citing specific sources." Honestly, I expected a faster, more accurate answer than I could get manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI's "Plausible" Analysis Report
&lt;/h3&gt;

&lt;p&gt;A few minutes later, the AI generated a report that &lt;em&gt;looked&lt;/em&gt; legitimate. Its conclusion:&lt;/p&gt;

&lt;p&gt;"The stock price drop is not due to a company-specific issue, but rather a sector-wide risk-off event."&lt;/p&gt;

&lt;p&gt;It even provided supporting evidence:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AI's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;plausible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;incorrect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;explanation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ai_response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;"conclusion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The drawdown is sector-wide, NOT company-specific."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Peers like Company A (-10.7%) and Company B (-14.1%) also dropped on the same day."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"It is unlikely that a Japan-specific catalyst would cause a US-based peer to fall 10.7%."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It cited Company A falling -10.7% and Company B falling -14.1% on the same day, arguing that "it's unlikely a Japan-specific catalyst would cause a US-based peer to drop over 10%." On the surface, it made sense.&lt;/p&gt;

&lt;p&gt;For a moment, I almost accepted it: "Ah, right, the market sentiment must have been bad." But -33%? That's an extreme drop. It was too abnormal to simply dismiss as a sector-wide issue.&lt;/p&gt;

&lt;p&gt;This gut feeling was what allowed me to uncover the AI's lie.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Answer Was in the Primary Source
&lt;/h3&gt;

&lt;p&gt;In the end, I did what I always do: I dug into the primary sources myself. I went to the company's IR site, and the answer was immediately there.&lt;/p&gt;

&lt;p&gt;"Notice Regarding Issuance of New Shares Through Third-Party Allotment."&lt;/p&gt;

&lt;p&gt;It was a public offering. And the dilution rate was a significant 15.79%. This was announced &lt;em&gt;after&lt;/em&gt; market close. No wonder the stock almost hit its limit down the next day. It was entirely a company-specific factor.&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ground&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;truth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;primary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ground_truth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;"catalyst"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Public stock offering announced after market close."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"impact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15.79% dilution of shares."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stock price dropped -15.02% the next day (limit down)."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI completely ignored this crucial primary information (the PDF disclosure) and instead picked up only secondary data (other companies' stock prices) from web searches, fabricating a plausible narrative.&lt;/p&gt;

&lt;p&gt;AI doesn't say "I don't know." It fabricates the most coherent story from the fragmented information it has. This was my personal experience with hallucination.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Limits of AI, and How to Use It Correctly
&lt;/h3&gt;

&lt;p&gt;This incident drastically changed my perspective on AI.&lt;/p&gt;

&lt;p&gt;In other cases, the AI &lt;em&gt;had&lt;/em&gt; correctly identified reasons for stock price changes. But even then, the evidence it presented was always news articles reported &lt;em&gt;after&lt;/em&gt; the stock had moved. In other words, AI can offer post-hoc explanations, but it cannot predict. Obvious, but a critical distinction.&lt;/p&gt;

&lt;p&gt;Blindly relying on AI for "answers" is too dangerous. So, how &lt;em&gt;should&lt;/em&gt; we use it?&lt;/p&gt;

&lt;p&gt;My conclusion: &lt;strong&gt;use it as an "aid" for human primary source research.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The role of my AI agent now isn't to "give answers." It's to "accelerate the process of finding answers."&lt;/p&gt;

&lt;p&gt;For example, I use it like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"From this 220,000-character financial report PDF, extract all mentions of 'impairment loss' and 'goodwill,' then summarize them chronologically."&lt;/li&gt;
&lt;li&gt;"From 5 years of timely disclosure data, list the dates and overviews of any announcements regarding 'new share issuance' or 'stock splits.'"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tasks like extracting keywords and organizing vast amounts of data, which would take a human several hours, are delegated to the AI. Based on the output, I perform the final analysis and make judgments myself. The AI is merely an excellent research assistant. I'm the driver.&lt;/p&gt;

&lt;p&gt;Never take AI-generated text at face value; always verify with primary sources. Skip this crucial step, and you'll eventually be tripped up by a "plausible lie" like I was.&lt;/p&gt;

&lt;p&gt;As someone who develops AI myself, I need to understand its limitations better than anyone. This failure was a valuable lesson that reinforced that understanding.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>My Investment Bot's Sell Rule Was Broken: How I Added SEC EDGAR Filings to Catch 'Pre-Bankruptcy' Signals Sooner</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Fri, 21 Aug 2026 23:30:18 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-investment-bots-sell-rule-was-broken-how-i-added-sec-edgar-filings-to-catch-pre-bankruptcy-5dbi</link>
      <guid>https://dev.to/masaoshimadaopen/my-investment-bots-sell-rule-was-broken-how-i-added-sec-edgar-filings-to-catch-pre-bankruptcy-5dbi</guid>
      <description>&lt;p&gt;Hey dev.to! Grandpa here. I'm a 38-year-old tinkering with AI agents and automated trading bots on weeknights and weekends.&lt;/p&gt;

&lt;p&gt;Today, I'm sharing a critical design flaw I found in my custom U.S. stock investment bot and how I fixed it. Specifically, my sell rule had a massive hole that could have let a stock plummet to near-bankruptcy before the bot took any action. Pretty scary stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Was Going On?
&lt;/h3&gt;

&lt;p&gt;My bot's sell logic was incredibly simple. Essentially, if a company missed earnings (EPS, revenue) consensus several times in a row, or if its growth rate significantly decelerated, the bot would sell. It was triggered by deteriorating fundamentals.&lt;/p&gt;

&lt;p&gt;On the surface, this seems reasonable. But the fragility of this logic became painfully clear when one of my monitored stocks took a sudden nosedive. The stock price started falling, and only much later did the news break that "terrible quarterly results" had come out.&lt;/p&gt;

&lt;p&gt;If I had owned that stock, my bot would have done nothing until the earnings report, just watching my capital evaporate. In hindsight, there were clear "pre-signals" long before the actual earnings deterioration: a CEO suddenly resigning, or an announcement of a capital raise that would significantly dilute shareholder value.&lt;/p&gt;

&lt;p&gt;As humans, we'd pick up on these signals: "Hmm, something's off with this company." But a bot only acts on programmed rules. And my rules were solely based on "earnings"—a &lt;strong&gt;lagging indicator&lt;/strong&gt; that only appears &lt;em&gt;after&lt;/em&gt; the event. This was a brutal design flaw.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Root Cause: Relying Only on Lagging Indicators
&lt;/h3&gt;

&lt;p&gt;The core of the problem was that I only considered "deteriorating business performance" as a single layer for my sell rules.&lt;/p&gt;

&lt;p&gt;I believe there are two main types of events that can destroy a company's value:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Deteriorating Business Performance (Tier A)&lt;/strong&gt;: Sales or profits don't meet targets. This is a result, which appears in numbers with a delay.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Capital/Governance Collapse (Tier B)&lt;/strong&gt;: Management leaves, fundraising that shakes the financial foundation, or delisting risk emerges. These are "anomalies" and can be &lt;strong&gt;leading indicators&lt;/strong&gt;, often occurring &lt;em&gt;before&lt;/em&gt; business performance shows up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My bot was only looking at Tier A. So, any Tier B event was completely ignored. It could only react defensively, realizing "something happened?" only after the stock price moved. This defeats the purpose of automated trading.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Monitoring Specific EDGAR Forms
&lt;/h3&gt;

&lt;p&gt;To address this, I decided to add a mechanism to mechanically detect these Tier B events.&lt;/p&gt;

&lt;p&gt;For U.S. stocks, companies are required to submit documents to the SEC (U.S. Securities and Exchange Commission) via the EDGAR system when significant events occur. Specifically, the "Form 8-K," an unscheduled material event report, describes major corporate changes in near real-time.&lt;/p&gt;

&lt;p&gt;However, reading through the massive volume of daily disclosures is impossible. So, I decided to monitor only &lt;strong&gt;specific Items&lt;/strong&gt; within these forms that signal particularly critical events.&lt;/p&gt;

&lt;p&gt;Here's how I defined these rules in a Python dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Tier B: Capital &amp;amp; Governance related sell triggers
&lt;/span&gt;&lt;span class="n"&gt;RULES_TIER_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Sudden departure of management is a significant risk
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;C-4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trigger_doc&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;8-K Item 5.02&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;event&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;Departure of Directors or Certain Officers (e.g., CEO, CFO)&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;action&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;FLAG_FOR_REVIEW&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;# Capital raises that can significantly dilute existing shareholder value
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;C-2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trigger_doc&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;8-K Item 3.02&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;event&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;Unregistered Sales of Equity Securities&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;action&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;FLAG_FOR_REVIEW&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;# Delisting notice. This requires immediate attention.
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;C-6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trigger_doc&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;8-K Item 3.01&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;event&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;Notice of Delisting or Failure to Satisfy a Continued Listing Rule or Standard&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;action&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;FLAG_FOR_REVIEW&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I integrated these rules into my monitoring bot. Now, if any of my holdings or watchlist stocks file these specific forms, I get an immediate notification.&lt;/p&gt;

&lt;p&gt;One crucial point here is that I didn't set &lt;code&gt;'action'&lt;/code&gt; to &lt;code&gt;SELL_IMMEDIATELY&lt;/code&gt;. I kept it as &lt;code&gt;FLAG_FOR_REVIEW&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because a CEO's resignation, for example, could be a positive generational change rather than a scandal. Making the rules too rigid can lead to missed opportunities. When a trigger fires, I leave room for human judgment to assess the final context. This intermediary step is quite important for operating a personal bot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learnings and Next Steps
&lt;/h3&gt;

&lt;p&gt;The lessons from this failure were significant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Design Sell Rules in Layers&lt;/strong&gt;: If you don't monitor across different layers—like "performance (lagging)" and "governance (leading)"—you'll be caught off guard.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Codify Human Intuition&lt;/strong&gt;: The key is how to translate an investor's gut feeling, like "a CEO resignation is bad," into objective triggers (specific disclosure documents) that a bot can interpret.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Position Sizing Discipline is the Strongest Defense&lt;/strong&gt;: Ultimately, no matter how refined your rules, unknown risks always exist. That's why enforcing position sizing discipline—not concentrating assets in a single stock—is the best damage control, enforced by the system itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Moving forward, my next challenge is to incorporate even harder-to-detect risks into the rules, like warrant liabilities hidden in financial statement footnotes (which are also time bombs for shareholder value).&lt;/p&gt;

&lt;p&gt;Personal development is a continuous cycle of trial and error. But because I'm operating with my own money, each failure becomes a valuable lesson. If I mess up again, I'll be sure to log it.&lt;/p&gt;

&lt;p&gt;Until next time,&lt;br&gt;
Grandpa&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>My AI Agent Saved Me from a 287% Revenue Growth Trap</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Wed, 19 Aug 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-ai-agent-saved-me-from-a-287-revenue-growth-trap-339e</link>
      <guid>https://dev.to/masaoshimadaopen/my-ai-agent-saved-me-from-a-287-revenue-growth-trap-339e</guid>
      <description>&lt;p&gt;Hey, it's Ojii. I'm a 38-year-old side-hustle engineer, tinkering with AI agents and automated trading bots on weeknights and weekends.&lt;/p&gt;

&lt;p&gt;Today, I want to share a story about how my homemade monitoring bot genuinely saved my bacon. I almost fell for a flashy headline and bought high. This is a real-world account of how my personal AI prevented me from making an emotional, knee-jerk decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Revenue +287%" Notification, Followed by...
&lt;/h3&gt;

&lt;p&gt;One morning, before the market opened, a stock I was watching released its earnings. The breaking news headline? "Revenue Up 287% Year-Over-Year."&lt;/p&gt;

&lt;p&gt;Whoa, seriously? That's insane growth.&lt;/p&gt;

&lt;p&gt;For a split second, my brain went into overdrive. Is this a train I can't afford to miss? I reflexively started to open my trading app. In these moments, humans are easily dominated by the fear of missing out.&lt;/p&gt;

&lt;p&gt;But then, a few minutes later, my custom bot pinged me on Slack.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Red flag found in 10-Q: going concern&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Seeing that, I immediately sobered up.&lt;br&gt;
"Substantial doubt about the company's ability to continue as a going concern"... that's the standard phrase indicating a risk of bankruptcy. Buried beneath that flashy headline was a serious bombshell.&lt;/p&gt;
&lt;h3&gt;
  
  
  What the Bot Was Doing
&lt;/h3&gt;

&lt;p&gt;What this monitoring bot does is actually very simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Periodically crawl the SEC's EDGAR database.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Detect new filings for companies on my watchlist.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Identify the type of filing (8-K, 10-Q, etc.).&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;If it's a 10-Q (quarterly report), extract the full text.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Search the text for predefined "red flag keywords."&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;If a match is found, send an alert to Slack.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most people, when they think of earnings announcements, look at the company's press releases or summaries (what's often called an 8-K). "Revenue +287%!" — those kinds of rosy numbers usually appear there. Companies want to appeal to investors, so they emphasize the good parts.&lt;/p&gt;

&lt;p&gt;However, the truly critical information is often hidden within the dry, hundreds-of-pages-long detailed financial statements—the 10-Q (quarterly report) or 10-K (annual report).&lt;/p&gt;

&lt;p&gt;My bot targets this "boring to read for humans, but super important primary source information."&lt;/p&gt;
&lt;h3&gt;
  
  
  Code for Detecting Red Flags
&lt;/h3&gt;

&lt;p&gt;The actual logic running is a Python snippet like this. It's just string searching; no LLMs involved. But it works perfectly well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Detect red flags from 10-Q text
# There are many other keywords, but these are representative
&lt;/span&gt;&lt;span class="n"&gt;red_flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;substantial doubt&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;going concern&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;material weakness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credit losses&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;restatement of financial statements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# sec_filings is assumed to be a list of filings obtained from EDGAR
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filing&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sec_filings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Target only quarterly reports (10-Q)
&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;filing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;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;10-Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Get full text from filing object
&lt;/span&gt;
    &lt;span class="c1"&gt;# Convert entire text to lowercase and check for keywords
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;red_flags&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;flag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="c1"&gt;# If found, immediately send Slack notification
&lt;/span&gt;        &lt;span class="nf"&gt;send_alert&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;Red flag found in 10-Q: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code detected the phrase "going concern" and stopped me from making an impulsive buy.&lt;/p&gt;

&lt;p&gt;Incidentally, the bot also compares revenue growth with operating expense growth. If revenue is +287% but costs are +400%, then it's just burning cash. The bot mechanically checks for this, to see if it's a "burn-rate" growth model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned This Time
&lt;/h3&gt;

&lt;p&gt;This incident re-emphasized several important points for me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Don't be swayed by headline numbers.&lt;/strong&gt; Good news is often amplified, bad news minimized. In the world of investing, consulting primary sources is absolutely critical.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Read primary sources mechanically.&lt;/strong&gt; It's simply impossible for a human to read hundreds of pages of reports thoroughly every time. We miss things when we're tired, and our judgment can be biased by pre-existing expectations. This kind of task is precisely what programs should do. They pick out facts dispassionately, without emotion.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The value of AI/automation lies in preventing oversights.&lt;/strong&gt; It's not about flashy stories like "predicting the future with the latest LLM!" Rather, it's about using systems to forcefully prevent the "careless mistakes" and "skipping tedious tasks" that all humans are prone to. I believe this is where the real value of personal development lies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the end, I didn't touch that stock. As expected, after a brief surge at open, it plummeted as the contents of the 10-Q became widely known. If it weren't for that bot alert, I would have been completely fleeced. That was a close call.&lt;/p&gt;

&lt;p&gt;I'll continue to build and nurture these kinds of unassuming but effective bots myself. Because I believe preventing one fatal mistake at a time, rather than chasing a flashy long shot, will probably take me further in the long run.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>"40% Annual Market Growth!" ...So Why Is the Entire Industry Losing Money? Data-Driven Dive Into the 'Quantity Grows, Profit Die</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Tue, 18 Aug 2026 23:30:19 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/40-annual-market-growth-so-why-is-the-entire-industry-losing-money-data-driven-dive-into-4hd3</link>
      <guid>https://dev.to/masaoshimadaopen/40-annual-market-growth-so-why-is-the-entire-industry-losing-money-data-driven-dive-into-4hd3</guid>
      <description>&lt;p&gt;Hey, it's your friendly neighborhood &lt;code&gt;ojii&lt;/code&gt;. I'm 38 and I spend my weekday evenings and weekends tinkering with AI trading bots.&lt;/p&gt;

&lt;p&gt;Building bots as a side hustle often puts me in a position to look at macro market data. "This industry is booming," or "that sector is in a freeze." Grasping these big trends helps sharpen my bot strategy.&lt;/p&gt;

&lt;p&gt;Recently, I stumbled upon some wild data. A certain market was reported to be "booming at an average annual growth rate of 40%!" By all accounts, this sounds like a gold rush. It looked like anyone who entered could make a fortune.&lt;/p&gt;

&lt;p&gt;But when I laid out the financial statements of the companies in that market, a brutal truth emerged: the market size was at an all-time high, yet major manufacturers were all reporting operating losses. How does this even happen? I got curious and dug deeper. Here's my thought process, uncensored.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Quantity" vs. "Price" Trap
&lt;/h3&gt;

&lt;p&gt;The first market I analyzed was solar panels.&lt;/p&gt;

&lt;p&gt;Data showed that global solar panel installations (quantity) skyrocketed by 3.8 times over a four-year period. That's incredible growth. Policy tailwinds clearly fueled an explosion in demand.&lt;/p&gt;

&lt;p&gt;However, looking at manufacturer profits over the same period, it was a bloodbath. Why? The answer was simple: prices had crashed.&lt;/p&gt;

&lt;p&gt;Market size is roughly determined by "Quantity (Q)" multiplied by "Price (P)." In this case, while Q exploded by +280%, P plummeted at an even faster rate. As a result, revenue stagnated or barely grew. Add development costs and personnel expenses, and profits sank into the red.&lt;/p&gt;

&lt;p&gt;A bizarre situation emerged: "market size is at an all-time high, but the industry's profit pool is negative."&lt;/p&gt;

&lt;p&gt;The first lesson here was: "&lt;strong&gt;Obligation creates quantity, but not price.&lt;/strong&gt;"&lt;br&gt;
Environmental policies and other "must-do" demands certainly force an increase in installation volume (Q). But this doesn't always lead to healthy price formation. In fact, it often attracts a flood of new entrants looking for subsidies, leading to cutthroat competition that destroys prices (P).&lt;/p&gt;

&lt;p&gt;When you hear "the market is growing," you need to break down whether that's growth in Q, P, or both, otherwise you'll misinterpret the core reality.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Hypothesis Falls Apart: Does Oligopoly Guarantee Profit?
&lt;/h3&gt;

&lt;p&gt;So, my next hypothesis was: "The reason they're dying from price competition is too many suppliers. If there were fewer players in an oligopolistic market, profits would be stable, right?"&lt;/p&gt;

&lt;p&gt;To test this, I pulled data for the DRAM (memory semiconductor) market. This market is a classic oligopoly, with the top three companies holding nearly 90% of the market share. This should be safe, I thought.&lt;/p&gt;

&lt;p&gt;But again, the data defied my expectations. The DRAM market cycled between "supercycles" of massive profits and "downturns" where the entire industry sank into the red, repeating every few years. Despite being an oligopoly, prices weren't stable; they were wildly volatile.&lt;/p&gt;

&lt;p&gt;This completely shattered my simple hypothesis that "fewer suppliers mean more profit."&lt;/p&gt;

&lt;p&gt;During this analysis, I ran a simple simulation in Python (pandas). I used code like this to see how changes in Q and P affected profit margins.&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_market_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Dummy function to calculate various metrics from market data
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Market size = Quantity * Price
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;market_size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Gross profit = Market size - (Quantity * Manufacturing Cost)
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gross_profit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;market_size&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unit_cost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Operating profit = Gross profit - Fixed costs
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operating_profit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gross_profit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fixed_cost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Operating margin
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operating_margin&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operating_profit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;market_size&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;100&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;

&lt;span class="c1"&gt;# Simulate with dummy data
&lt;/span&gt;&lt;span class="n"&gt;dummy_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;year&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="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;380&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Quantity surges
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&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="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;      &lt;span class="c1"&gt;# Price crashes
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unit_cost&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="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;# Costs also decrease, but...
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fixed_cost&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;result_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_market_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dummy_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;year&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;market_size&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;operating_profit&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;operating_margin&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;Tinkering with this code made it clear that even if Q grows, if P's rate of decline outpaces it, profit margins will dive into negative territory.&lt;/p&gt;

&lt;h3&gt;
  
  
  The True Divergence Point: "Speed of Supply Response"
&lt;/h3&gt;

&lt;p&gt;What differentiates solar panels (many suppliers, losing money) from DRAM (few suppliers, boom-and-bust cycles)?&lt;/p&gt;

&lt;p&gt;After much thought, I arrived at the conclusion: the &lt;strong&gt;"speed at which supply can respond to changes in demand,"&lt;/strong&gt; a time-based perspective.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DRAM (Semiconductors)&lt;/strong&gt;: Even if demand surges, building a new fabrication plant (fab) takes 2-3 years. This creates a period where supply can't keep up with demand. During this time, shortages drive prices sky-high, and manufacturers rake in massive profits – the supercycle. However, seeing this, companies all invest in increasing production, leading to an oversupply 2-3 years later, causing prices to crash – the downturn. The long lead time for supply creates periodic mismatches between supply and demand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solar Panels&lt;/strong&gt;: These don't require as massive capital investment as DRAM (relatively speaking). The lead time to retool existing factories or add new lines is shorter. Therefore, even with increased demand, supply can catch up relatively quickly. As a result, price surges due to shortages are less likely, and the market often falls into price competition due to constant oversupply.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, the true divergence point wasn't just "structural variables" like the number of players, but a "cyclical variable": the speed of supply's response to demand.&lt;/p&gt;

&lt;p&gt;This perspective brings clarity to other markets. For example, SAF (Sustainable Aviation Fuel) might have a relatively fast supply response because existing oil refining facilities can be repurposed. If so, it suggests a market structure less prone to DRAM-like supercycles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The phrase "the market is growing" is almost meaningless in the context of investment or development. You need to break it down further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Is the growth driven by "quantity" or "price"?&lt;/li&gt;
&lt;li&gt;  What is the "speed of supply response" in that market—fast or slow?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only by dissecting it to this level can you truly see whether the market structure is set up for profit or if a brutal war of attrition awaits.&lt;/p&gt;

&lt;p&gt;This analysis offered direct feedback for my own automated trading bot strategies. When deciding which sectors to focus on and what time horizons to consider, I'll be sure to remember this "cyclical variable" perspective.&lt;/p&gt;

&lt;p&gt;This is just my personal analysis log, but I hope it's helpful to someone out there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>My Trading Bot "Perma-Held" a Delisted Stock: The Silent Bug of Disappearing Historical Data</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Mon, 17 Aug 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-trading-bot-perma-held-a-delisted-stock-the-silent-bug-of-disappearing-historical-data-3fpk</link>
      <guid>https://dev.to/masaoshimadaopen/my-trading-bot-perma-held-a-delisted-stock-the-silent-bug-of-disappearing-historical-data-3fpk</guid>
      <description>&lt;p&gt;Hey there, it's your friendly neighborhood 'Ojii' (old man). I'm 38, a corporate drone during the week, and spend my weekends tinkering with AI trading bots.&lt;/p&gt;

&lt;p&gt;While doing routine maintenance on my weekend bots, I noticed an unfamiliar stock lingering in my portfolio. "Huh, when did I even enter this position?" I wondered.&lt;/p&gt;

&lt;p&gt;Turns out, it was a stock that had been delisted several months ago. My bot wasn't selling it; it was just quietly "perma-holding" it. The P&amp;amp;L was negligible, but having a system maintain unintended positions is a serious issue. I immediately started investigating.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptom: Silently Vanishing from the Exit Logic
&lt;/h3&gt;

&lt;p&gt;First, I dove into the logs. I found that at a certain point, the stock had completely disappeared from the list of assets considered for exit decisions. No errors. It was simply treated as if it no longer existed.&lt;/p&gt;

&lt;p&gt;The root cause lay with the external API my bot used to fetch stock price data. When a stock is delisted, that API stops returning its historical data. That's a pretty standard API behavior.&lt;/p&gt;

&lt;p&gt;The problem was in my code. A fundamental condition for triggering the exit logic was "having at least 260 days of candlestick data." This was necessary for calculating various technical indicators.&lt;/p&gt;

&lt;p&gt;What happened to stocks that didn't meet this condition? They weren't throwing exceptions; they were simply skipped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Problematic code: If data is less than 260 days, it's not even considered for exit
&lt;/span&gt;&lt;span class="n"&gt;frames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALL_TICKERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Attempts to fetch 2 years of data from an external API
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2y&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="c1"&gt;# If data cannot be fetched or is less than 260 days, it's filtered out here
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;260&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;

&lt;span class="c1"&gt;# ... Subsequent logic completely ignores tickers not in `frames`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;if&lt;/code&gt; statement was the culprit. Stocks delisted and thus no longer providing data were silently excluded from processing here. No error logs, just a single warning log line. How could I have noticed? A classic silent bug pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cause: Inconsistency Between Two APIs
&lt;/h3&gt;

&lt;p&gt;What made things even more complicated was the way I was using two different APIs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Decision-making API&lt;/strong&gt;: Used for technical analysis and exit decisions (e.g., historical price data). &lt;strong&gt;-&amp;gt; Data vanished from here&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Valuation API&lt;/strong&gt;: Used for portfolio valuation and actual position management (e.g., current holdings). &lt;strong&gt;-&amp;gt; The position continued to exist here&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The inconsistency between these two data sources critically delayed bug detection. In one world, it was a "non-existent stock," but in the other, it was an "owned asset." There was no mechanism to detect this discrepancy. That's brutal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix: Position-Driven Data Freshness Check
&lt;/h3&gt;

&lt;p&gt;The fundamental problem was the flow: "first, gather data for all stocks, then select those for processing." This approach meant that the moment a stock disappeared from the data source, its existence became untraceable.&lt;/p&gt;

&lt;p&gt;So, I reversed the order of operations. The new flow is: "first, list all currently held positions. Then, for each of those stocks, check if data can be properly retrieved."&lt;/p&gt;

&lt;p&gt;If data cannot be retrieved or is too old, it's treated as an "abnormal situation." It's either forcibly marked for exit, or at the very least, a critical error notification is triggered.&lt;/p&gt;

&lt;p&gt;Here's the conceptual code for the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Revised conceptual code: Check data freshness individually for held positions
&lt;/span&gt;&lt;span class="n"&gt;live_positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_current_positions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Fetch current holdings from brokerage API
&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;live_positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Attempt to fetch data individually
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&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="n"&gt;period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2y&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="c1"&gt;# If data is missing or insufficient, mark as "stale"
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;df&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;260&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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;Data for position &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is stale or missing. Marked for investigation.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="c1"&gt;# ... If data is normal, proceed with regular exit logic
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that stocks that disappear from the data source are no longer left unattended. Stocks marked as &lt;code&gt;stale&lt;/code&gt; can then be manually checked, liquidated, or routed to a separate emergency handling flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned: No Data is an Error
&lt;/h3&gt;

&lt;p&gt;Three key lessons I took away from this incident:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Design for Data Source Inconsistencies&lt;/strong&gt;: In systems using multiple APIs, data might vanish from one but remain in another. Especially when dealing with assets that have a lifecycle (like listing/delisting), a mechanism to detect this inconsistency is crucial.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Don't Tolerate Silent Errors&lt;/strong&gt;: Designs using &lt;code&gt;try-except-pass&lt;/code&gt; or continuing processing with just a warning log when conditions aren't met are convenient during development but become time bombs in production. For critical processes, explicitly fail or send alerts (e.g., to Slack).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Seriously Handle "Non-Existence"&lt;/strong&gt;: If your logic assumes data "exists," it will break when it "disappears." The state of "data cannot be retrieved" isn't just a skip condition; it's a critical signal that the system is in an unexpected situation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you're coding side projects late at night or on weekends, it's easy to get lazy with error handling. But skimping on it here can lead to much larger time losses later. Good learning experience.&lt;/p&gt;

&lt;p&gt;Hope this helps other independent bot developers out there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>AI Task Done, Output File Corrupted?! How Preserving 'Transcripts' Saved My Data</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Fri, 14 Aug 2026 23:30:16 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/ai-task-done-output-file-corrupted-how-preserving-transcripts-saved-my-data-3hbc</link>
      <guid>https://dev.to/masaoshimadaopen/ai-task-done-output-file-corrupted-how-preserving-transcripts-saved-my-data-3hbc</guid>
      <description>&lt;p&gt;Hey everyone, it's me, your friendly neighborhood senior dev. I've been running some AI agents for my side hustle during weeknights, and recently had a heart-stopping moment I wanted to log and share.&lt;/p&gt;

&lt;p&gt;My market analysis agent, which takes several hours to complete, sent a Slack notification that its task was done. "Great!" I thought, and opened the final report file it generated. To my horror, the content was truncated midway. The file size was also suspiciously small.&lt;/p&gt;

&lt;p&gt;My blood ran cold for a second. This task involves a fair number of LLM API calls and a long execution time. Honestly, rerunning it was out of the question, both in terms of time and cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cause Unknown, But "Writes Can Fail" Is a Reality
&lt;/h3&gt;

&lt;p&gt;First, I tried to figure out why the file was corrupted. Looking at the agent's execution logs, the process exited with a normal &lt;code&gt;0&lt;/code&gt; status code. No exceptions were thrown.&lt;/p&gt;

&lt;p&gt;Possible culprits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The process was killed by some external factor while writing a large amount of text data to the file.&lt;/li&gt;
&lt;li&gt;The process terminated before the I/O buffer was flushed to storage.&lt;/li&gt;
&lt;li&gt;...or something along those lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, pinpointing the root cause is difficult. But the important thing is the fact that "final writes to a file are not always guaranteed to succeed." Especially when writing large texts of tens of thousands of characters at once with a single &lt;code&gt;write()&lt;/code&gt; call, there's always a risk of it ending in an incomplete state for some reason.&lt;/p&gt;

&lt;p&gt;In this case, data salvage was my top priority, more so than root cause analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lifeline: 'Transcripts'
&lt;/h3&gt;

&lt;p&gt;This is where I really had to pat myself on the back: I had implemented a "transcript" design that logs the agent's entire thought process to a file.&lt;/p&gt;

&lt;p&gt;This transcript includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The initial prompt given to the agent&lt;/li&gt;
&lt;li&gt;The Chain of Thought&lt;/li&gt;
&lt;li&gt;The Python code executed&lt;/li&gt;
&lt;li&gt;The standard output of the code execution&lt;/li&gt;
&lt;li&gt;The history of API calls with the LLM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...Essentially, a complete, chronological record of everything from task start to finish, appended to a log file.&lt;/p&gt;

&lt;p&gt;And sure enough, when I rummaged through this log, there it was: the log of the step where the agent generated the final report. At the end of the transcript, along with a &lt;code&gt;## Final Report ##&lt;/code&gt; marker, the full text of the generated report was perfectly recorded.&lt;/p&gt;

&lt;p&gt;The transcript file was written in append mode step-by-step during processing, so even if something failed at the very last moment, all logs up to that point remained intact. This truly became my lifeline.&lt;/p&gt;

&lt;p&gt;I discarded the corrupted final output file and simply copied and pasted the report section from the transcript into a new file. This saved me hours of work and API costs. Seriously, I was so relieved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing Automated Validation and Recovery
&lt;/h3&gt;

&lt;p&gt;While manually recovering the data was good, I really didn't want to be woken up in the middle of the night if the same thing happened again. So, I decided to automate this recovery process.&lt;/p&gt;

&lt;p&gt;Specifically, I added a step at the end of the agent's task flow: "Output file validation and automatic recovery on failure."&lt;/p&gt;

&lt;p&gt;Here's what the code looks 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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_and_recover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transcript_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Validates the output file and attempts recovery from the transcript if corrupted.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# For this case, a simple validation: &amp;gt; 40k characters and a specific marker at the end
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;40000&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;END_OF_REPORT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content&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;Output file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is valid.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt; &lt;span class="c1"&gt;# File is good
&lt;/span&gt;    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;IOError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;UnicodeDecodeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# If file doesn't exist or can't be read (corrupted), proceed to recovery
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="c1"&gt;# Recovery logic starts here
&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;Output file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is corrupted or missing. Attempting recovery from transcript...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&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;f_trans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;full_transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f_trans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;# Extract the final report section from the transcript
&lt;/span&gt;            &lt;span class="n"&gt;report_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;full_transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;## Final Report ##&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;report_start&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;recovered_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;full_transcript&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;report_start&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
                &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&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;f_out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;f_out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recovered_content&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;Successfully recovered the report to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IOError&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;Error: Transcript file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;transcript_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="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;Recovery failed.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; First, try to open the final output file. If it can't be opened, or if its content doesn't match the expected format (in this case, character count and an end marker), it's considered abnormal.&lt;/li&gt;
&lt;li&gt; If abnormal, open the transcript file.&lt;/li&gt;
&lt;li&gt; Extract the report section from the transcript using regex or string search.&lt;/li&gt;
&lt;li&gt; Overwrite the original output file with the extracted content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By calling this function at the end of the task, if the file write ever fails, it will attempt to self-heal automatically before passing control to the next process. This increased the robustness of my system by a notch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson Learned: Proactive, Defensive Programming Saves Future You
&lt;/h3&gt;

&lt;p&gt;The lesson I learned from this incident is subtle but crucial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;For high-cost, non-idempotent processes, don't trust the final output alone.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keeping a "transcript" of all thought processes and intermediate generations serves not only for debugging but also as insurance for data recovery.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It's essential to have a validation step (not just "generate and done") to ensure the generated output is complete. If possible, implementing a self-healing mechanism allows you to sleep soundly at night.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially in personal development, working with limited time and resources, unexpected troubles like this can be a real mental drain. It reinforced in me the importance of putting in a little extra effort now for defensive design, to make things easier for my future self.&lt;/p&gt;

&lt;p&gt;I'll write again if I mess something up. Cheers. 👨‍💻&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Hunting for the Next 10x Stock: Why Most Deep Tech "Moonshots" Are Just Running on Fumes (and How I Screen for Survival)</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Wed, 12 Aug 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/hunting-for-the-next-10x-stock-why-most-deep-tech-moonshots-are-just-running-on-fumes-and-how-i-5453</link>
      <guid>https://dev.to/masaoshimadaopen/hunting-for-the-next-10x-stock-why-most-deep-tech-moonshots-are-just-running-on-fumes-and-how-i-5453</guid>
      <description>&lt;p&gt;Hey, it's oji_ai_dev here!&lt;/p&gt;

&lt;p&gt;I usually tinker with FX and Japanese stock short-term trading bots, but every now and then, I look for long-term holds. The thing is, standard stock screening metrics like P/E or ROE usually filter for established, profitable companies. Nothing wrong with that, but I crave something more: companies currently in the red, but sitting on next-gen tech that could be commonplace in a decade. It’s a moonshot, sure, but if it hits, it hits big.&lt;/p&gt;

&lt;p&gt;So, I completely changed my approach. I started looking for these high-risk, high-reward companies, explicitly ignoring profitability. And man, did I get an education. Most of them were on the brink of running out of cash.&lt;/p&gt;




&lt;h3&gt;
  
  
  How Do You Value an Unprofitable Company? — Shifting from "Profit" to "Survival"
&lt;/h3&gt;

&lt;p&gt;The first hurdle was valuation. Most unprofitable tech companies are valued by PSR (Price/Sales Ratio), but some are still in pure R&amp;amp;D, with no revenue yet. At that point, there's nothing quantitative to measure. You're left with qualitative factors: "the CEO has a great pitch" or "the tech sounds amazing."&lt;/p&gt;

&lt;p&gt;Investing based solely on that is a gamble. As an engineer, I need quantitative criteria. So, I shifted my focus from "profitability" to "survival probability." No matter how groundbreaking the tech, if the company goes bust, the stock becomes worthless. To wait 10 years for a technology to mature, the absolute prerequisite is that the company must stay in business for those 10 years.&lt;/p&gt;

&lt;p&gt;To measure this "survival probability," I built a screening script using three key metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Survival Metric 1: Cash Runway
&lt;/h3&gt;

&lt;p&gt;This is the simplest and most crucial: "How many years can the company survive?"&lt;/p&gt;

&lt;p&gt;The formula is &lt;code&gt;Cash &amp;amp; Equivalents ÷ Quarterly Cash Burn Rate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if a company has ¥1 billion in cash and burns ¥100 million per quarter, its runway is 10 quarters, or 2.5 years.&lt;/p&gt;

&lt;p&gt;I aimed for a minimum of 2 years. Clinical trials, large-scale proof-of-concept projects, developing next-gen tech—it all takes time. A company that runs out of funding in less than 2 years is game over the moment its next fundraising round fails, no matter how promising the tech. I filtered these out first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Survival Metric 2: Dilution Rate
&lt;/h3&gt;

&lt;p&gt;Even with a long runway, there's a hidden trap: dilution.&lt;/p&gt;

&lt;p&gt;Unprofitable companies can't get bank loans, so they typically raise capital by issuing new shares (equity financing). This dilutes the value of existing shares.&lt;/p&gt;

&lt;p&gt;If a company's value stays the same but its share count doubles, the per-share value halves. Companies that issue shares aggressively every year might see their stock price stagnate or even fall, even if the business grows a little. They become a cash sink for existing shareholders.&lt;/p&gt;

&lt;p&gt;So, I checked the historical annual share count increase. If it exceeded 20% annually, it's likely the company's business model relies on siphoning money from shareholders. These highly dilutive companies were also excluded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Survival Metric 3: Real Customer Validation
&lt;/h3&gt;

&lt;p&gt;Many tech companies release news like, "Started PoC (Proof of Concept) with a major corporation!" or "Signed MOU (Memorandum of Understanding) regarding X!"&lt;/p&gt;

&lt;p&gt;While they sound good, these are often just "trial" stages and haven't generated a single yen in revenue. The true market need for a technology is only proven when someone is willing to pay serious money for it.&lt;/p&gt;

&lt;p&gt;Therefore, I checked for "backlog" or "delivery contracts"—evidence of real, paying customers. Companies with only PoCs or MOUs are still in the dream-story phase. These were also filtered out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screening 107 Companies: The Results
&lt;/h3&gt;

&lt;p&gt;I added one more condition: "Is the stock price overheated yet? (1-year return &amp;lt; +100%)." Then, I ran this combined screen on 107 companies in a specific next-gen tech sector.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of the logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Survival Screening Logic for Unprofitable Tech&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;screenCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runway_years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cash&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quarterly_burn&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dilution_rate_pa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shares_yoy_growth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;has_real_customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backlog&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="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has_delivery_contracts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runway_years&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL: Runway too short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dilution_rate_pa&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL: High dilution&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;has_real_customers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL: Customers are not real (PoC/MOU only)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;one_year_return&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL: Already hyped&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PASS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results were far stricter than I imagined.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Most companies failed on "Cash Runway less than 2 years." Their cash burn was simply unsustainable.&lt;/li&gt;
&lt;li&gt;  Even those that passed the runway test, a significant number failed on "Dilution Rate exceeding 20%."&lt;/li&gt;
&lt;li&gt;  Filtering further by "Real Customer Validation" left almost nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately, only &lt;strong&gt;one company&lt;/strong&gt; passed all the conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson Learned: This "Treasure Hunt" Was Really a Mine-Clearing Operation
&lt;/h3&gt;

&lt;p&gt;Calling it a "hunt for the next 10x stock" sounds exciting, but what I was actually doing was a full-blown mine-clearing operation. Behind every glittering tech story were precarious financial situations, ready to explode at any moment.&lt;/p&gt;

&lt;p&gt;This screening process hammered home how dangerous it is to invest based solely on qualitative "hope" and "stories." The correct sequence, I now believe, is to first use quantitative "survival metrics" to cut out the deadwood. Only then, with a pool of companies with a high probability of survival, can you begin to compare their technological advantages.&lt;/p&gt;

&lt;p&gt;This script is still rough, but for individual developers like me to compete against big capital, we have to build these niche analytical tools ourselves and carve out our own edge.&lt;/p&gt;

&lt;p&gt;No one knows if this one company will truly be a multi-bagger, but at least I can sleep soundly with this portfolio. I'll share more updates if anything develops.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>My Investment Screener's 'Exclusion List' Was Full of Zombies (Literally)</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Sun, 09 Aug 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-investment-screeners-exclusion-list-was-full-of-zombies-literally-3gn9</link>
      <guid>https://dev.to/masaoshimadaopen/my-investment-screeners-exclusion-list-was-full-of-zombies-literally-3gn9</guid>
      <description>&lt;p&gt;Hey everyone, it's your resident 38-year-old 'oji' here, tinkering with AI agents and automated trading bots in the evenings and on weekends.&lt;/p&gt;

&lt;p&gt;Today, I want to share a story about a quiet, unassuming bug in my self-built investment screener. It's a classic case where not a single line of code was wrong, yet the overall system continued to behave in an unintended way.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Discovery: A Hunch About the 'Exclusion List'
&lt;/h3&gt;

&lt;p&gt;I run a personal automated stock screener. It pulls data for all listed stocks, filters them based on my custom criteria, and narrows down potential candidates for monitoring. Pretty standard stuff.&lt;/p&gt;

&lt;p&gt;Within this script, there was a static list of specific stock tickers to be mechanically excluded. For example, companies whose industry doesn't align with my strategy, or ones I'd previously researched and decided weren't a good fit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# johnny_screen.py (conceptual code illustrating the problem)
&lt;/span&gt;
&lt;span class="c1"&gt;# This list was not maintained
&lt;/span&gt;&lt;span class="n"&gt;KNOWN_EXCLUSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;6641&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Nissin Electric Co., Ltd. (Delisted 2023-04-27)
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9161&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# ID&amp;amp;E (Found from logs, also delisted)
&lt;/span&gt;    &lt;span class="c1"&gt;# ... many more defunct tickers mixed in
&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;screen_stocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stocks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Exclude stocks whose ticker is in KNOWN_EXCLUSIONS
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stocks&lt;/span&gt; &lt;span class="k"&gt;if&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;ticker&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;KNOWN_EXCLUSIONS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the code is incredibly simple. Any ticker in &lt;code&gt;KNOWN_EXCLUSIONS&lt;/code&gt; is simply filtered out from the screening process.&lt;/p&gt;

&lt;p&gt;Recently, while reviewing some of the screener's logic, this list caught my eye. "Hmm, when was the last time I updated this list?" I wondered.&lt;/p&gt;

&lt;p&gt;To be honest, I hadn't touched it much since I first created it. It was working, no errors, so I didn't give it a second thought. And that, my friends, was the quiet beginning of a silent bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cause: Tickers Delisted 2 Years Ago Were Still in the List
&lt;/h3&gt;

&lt;p&gt;I had a bad feeling, so I started looking up each ticker code in the list. And boy, did I find some.&lt;/p&gt;

&lt;p&gt;"I don't recognize this stock..." I thought, only to Google it and find it was delisted in 2023.&lt;br&gt;
"And this one... oh wow, it was acquired and delisted more than two years ago!"&lt;/p&gt;

&lt;p&gt;The list contained multiple tickers of "zombie companies" that had long since vanished from the market. In hindsight, this was quite problematic.&lt;/p&gt;

&lt;p&gt;Here's how the script behaved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; It fetches a list of all currently listed stocks (around 4000). &lt;/li&gt;
&lt;li&gt; For each stock, it checks if its ticker is in the &lt;code&gt;KNOWN_EXCLUSIONS&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt; Naturally, delisted tickers are &lt;em&gt;not&lt;/em&gt; present in the comprehensive list from step 1.&lt;/li&gt;
&lt;li&gt; Therefore, the &lt;code&gt;if s.ticker not in KNOWN_EXCLUSIONS&lt;/code&gt; check would &lt;em&gt;never&lt;/em&gt; encounter an issue with these zombie tickers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code never threw an error. Nothing appeared in the logs. It just diligently performed a pointless check every single day, trying to avoid non-existent enemies.&lt;/p&gt;

&lt;p&gt;This is the definition of a "silent bug." The logic itself is correct, but the &lt;em&gt;data&lt;/em&gt; or &lt;em&gt;configuration&lt;/em&gt; it relies on becomes stale due to real-world changes, leading the entire system into an unintended and inefficient state. Running automated systems, you sometimes fall into these traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix and the Lesson: Configuration Files Have an 'Expiration Date'
&lt;/h3&gt;

&lt;p&gt;The solution was simple. First, I manually went through the list and removed all delisted tickers.&lt;/p&gt;

&lt;p&gt;But that alone isn't a fundamental fix. If I forget about this list again, in two years it'll be full of new zombies.&lt;/p&gt;

&lt;p&gt;So, I decided to build in a mechanism to periodically check the validity of this "exclusion list." Specifically, I added a simple script that runs before the main screener. It checks if each ticker in &lt;code&gt;KNOWN_EXCLUSIONS&lt;/code&gt; actually exists in the current list of listed stocks. If a ticker doesn't exist, it logs a warning.&lt;/p&gt;

&lt;p&gt;This experience boiled down to one key lesson:&lt;br&gt;
&lt;strong&gt;"Code might be right, but data rots."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Especially static configuration files that depend on external environments (like corporate mergers, acquisitions, or delistings in this case) will definitely become a cancer in your system if left unattended.&lt;/p&gt;

&lt;p&gt;"It worked once, so it's fine" is not enough. There's no guarantee that configuration will remain "valid" a year or two down the line. I needed to adopt the mindset that configuration files, too, have an "expiration date."&lt;/p&gt;

&lt;p&gt;When building automated systems, you need to design not only the logic but also the mechanisms for how to maintain the "freshness" of the settings and data it depends on, or how to detect when they've gone stale. Think of it as a self-diagnosis feature.&lt;/p&gt;

&lt;p&gt;There might be similar, silently rotting configuration files lurking in systems at your workplace, forgotten by everyone. Sometimes, taking a look might lead to interesting discoveries.&lt;/p&gt;




&lt;p&gt;Oji @oji_ai_dev&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>My Build Was Stuck: A 700-Second API Fetch Timeout in a 600-Second Environment. Here's How I Fixed It with a Self-Healing Cache.</title>
      <dc:creator>oji - building AI in public</dc:creator>
      <pubDate>Sat, 08 Aug 2026 23:30:22 +0000</pubDate>
      <link>https://dev.to/masaoshimadaopen/my-build-was-stuck-a-700-second-api-fetch-timeout-in-a-600-second-environment-heres-how-i-fixed-4351</link>
      <guid>https://dev.to/masaoshimadaopen/my-build-was-stuck-a-700-second-api-fetch-timeout-in-a-600-second-environment-heres-how-i-fixed-4351</guid>
      <description>&lt;p&gt;Hey everyone, it's your friendly neighborhood old man dev here. I'm 38, working as an engineer during the week, and building AI algo trading bots on the weekends.&lt;/p&gt;

&lt;p&gt;Sometimes, when I'm building personal projects like these bots, I'll notice something like, "Hmm, this thing hasn't been spitting out logs lately." That usually means it's silently halted. And that's exactly what happened this time.&lt;/p&gt;

&lt;p&gt;The task was simple: regularly fetch data for about 250 tickers from a certain market API. It &lt;em&gt;should&lt;/em&gt; have been straightforward. But the logs showed it was stopping midway every single time. No completion logs whatsoever.&lt;/p&gt;

&lt;p&gt;My first thought was a momentary network interruption or a temporary API server glitch. But after multiple retries, it kept failing at the same spot. A deeper dive revealed a much simpler, more fundamental design flaw.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Root Cause: A Marathon Designed to Fail
&lt;/h3&gt;

&lt;p&gt;The reason was, quite simply, a timeout.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Time to fetch data for 1 ticker: Average 2.8 seconds&lt;/li&gt;
&lt;li&gt;  Number of tickers to fetch: 250&lt;/li&gt;
&lt;li&gt;  Total theoretical time to fetch everything: 2.8 seconds × 250 = &lt;strong&gt;700 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meanwhile, the execution environment where this bot runs has a timeout setting of &lt;strong&gt;600 seconds&lt;/strong&gt; (10 minutes).&lt;/p&gt;

&lt;p&gt;I think you can see the problem now. I was running a process that &lt;em&gt;requires 700 seconds to complete&lt;/em&gt; in an environment that &lt;em&gt;forcibly terminates after 600 seconds&lt;/em&gt;. Of course, it would never finish. It was entirely my design fault. D'oh!&lt;/p&gt;

&lt;p&gt;Why did I overlook such a basic thing? During development, I was testing with only about 10 tickers. 10 tickers would finish in just under 28 seconds. "Yep, it works, good to go!" I thought. Then, when I scaled it up to the full 250 tickers for production, I completely skipped calculating the total time it would take.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Not Just a Cache, a "Self-Healing" Cache
&lt;/h3&gt;

&lt;p&gt;When faced with this kind of problem, the first thing that comes to mind is, naturally, caching. Store the fetched data locally, and for subsequent runs, reuse the local data instead of hitting the API.&lt;/p&gt;

&lt;p&gt;But in this case, that alone wasn't enough.&lt;/p&gt;

&lt;p&gt;Why? Because the &lt;em&gt;very first run&lt;/em&gt; to build the cache would still take 700 seconds and hit the timeout. When the process crashes due to a timeout, any partially fetched data in memory simply vanishes. So, the next time it starts, it would have to re-fetch all 250 tickers from scratch, leading to another timeout... an infinite loop where the cache never gets built.&lt;/p&gt;

&lt;p&gt;That's where the idea of a 'self-healing cache' came in.&lt;/p&gt;

&lt;p&gt;What I did was incredibly simple: "Save progress frequently at logical checkpoints."&lt;/p&gt;

&lt;p&gt;Specifically, after fetching every 50 tickers, I would write the results obtained so far to a file in pickle format.&lt;/p&gt;

&lt;p&gt;With this approach, even if the process is interrupted by the 600-second timeout, at least &lt;code&gt;50 × N&lt;/code&gt; items of progress remain on disk. &lt;/p&gt;

&lt;p&gt;On the next startup, it first loads this cache file. Then, it only goes to the API to fetch the tickers that are &lt;em&gt;not yet present&lt;/em&gt; in the file. &lt;/p&gt;

&lt;p&gt;By repeating this, no matter how many timeouts occur, the cache for all 250 tickers will eventually be completed. Even if the task is interrupted, it can resume from where it left off.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Code: Saving Progress Every 50 Items
&lt;/h3&gt;

&lt;p&gt;Here's what the actual code looks 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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;CACHE_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api_data_cache.pkl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;CACHE_TTL_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;  &lt;span class="c1"&gt;# Cache is valid for 6 days
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_data_with_self_healing_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickers_to_fetch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    A caching mechanism that saves intermediate progress every 50 items
    so the process can resume even if interrupted for a long time.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="c1"&gt;# Load existing cache if it exists (and is within TTL)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CACHE_FILE&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getmtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CACHE_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;CACHE_TTL_SECONDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CACHE_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&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="c1"&gt;# Create a list of tickers that truly need to be fetched via API this time
&lt;/span&gt;    &lt;span class="n"&gt;needed_tickers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickers_to_fetch&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cache&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;Total: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickers_to_fetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Cached: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, To Fetch: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;needed_tickers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fetched_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;needed_tickers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# data = fetch_from_external_api(ticker) # This is the time-consuming API call
&lt;/span&gt;            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt; &lt;span class="c1"&gt;# Using dummy data for this example
&lt;/span&gt;            &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
            &lt;span class="n"&gt;fetched_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

            &lt;span class="c1"&gt;# ★★★ Core part of self-healing ★★★
&lt;/span&gt;            &lt;span class="c1"&gt;# Save cache to disk every time 50 new items are fetched
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fetched_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fetched_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Saving intermediate cache progress (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fetched_count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; new items) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CACHE_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error fetching &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&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;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Saving progress before exit.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# Even if an error occurs, save progress before re-raising the exception
&lt;/span&gt;            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CACHE_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&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="k"&gt;raise&lt;/span&gt;

    &lt;span class="c1"&gt;# Finally, save all results
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fetched_count&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="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;--- Saving final cache ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CACHE_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&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="c1"&gt;# Return data for all requested tickers
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickers_to_fetch&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is the &lt;code&gt;fetched_count % 50 == 0:&lt;/code&gt; part, where I regularly write progress to a file. It's also subtly important to include the save operation in the &lt;code&gt;except&lt;/code&gt; clause. This ensures that even if an unexpected error occurs, all the hard work up to that point isn't lost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results and Lessons Learned
&lt;/h3&gt;

&lt;p&gt;With this fix, the bot now runs stably.&lt;/p&gt;

&lt;p&gt;As expected, the initial run timed out once. But looking at the logs, about 200 items of data were correctly saved to the cache file. On the second run, it fetched the remaining 50 items, and the cache for all tickers was successfully completed.&lt;/p&gt;

&lt;p&gt;And the runs &lt;em&gt;after&lt;/em&gt; that were wild. &lt;/p&gt;

&lt;p&gt;With a 100% cache hit rate, there are zero API calls. The process that previously couldn't finish even in 600 seconds now completes in an &lt;strong&gt;average of 2.8 seconds&lt;/strong&gt;. That's not just 10x faster – it's an order of magnitude improvement.&lt;/p&gt;

&lt;p&gt;The lesson I learned from this is the importance of designing with the assumption that "tasks will be interrupted." Especially in personal development, cloud environments, or home servers, you never know when things might go down. When writing long-running processes, you absolutely must consider "resumption from interruption" as part of the design.&lt;/p&gt;

&lt;p&gt;This "self-healing" concept can be applied in many situations beyond API fetching, like heavy data analysis batch jobs or processing tens of thousands of files.&lt;/p&gt;

&lt;p&gt;If you're struggling with a "long-running task that inexplicably never finishes," I hope this helps you out!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
