<?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>Forem: Mixa_Dev</title>
    <description>The latest articles on Forem by Mixa_Dev (@mixa_dev).</description>
    <link>https://forem.com/mixa_dev</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3935396%2Fa2afa3fd-51cf-4c40-8693-a4d3051dbeda.png</url>
      <title>Forem: Mixa_Dev</title>
      <link>https://forem.com/mixa_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mixa_dev"/>
    <language>en</language>
    <item>
      <title>My Alert System Fired for the First Time. The Engine Was Working Perfectly.</title>
      <dc:creator>Mixa_Dev</dc:creator>
      <pubDate>Tue, 19 May 2026 15:24:35 +0000</pubDate>
      <link>https://forem.com/mixa_dev/my-alert-system-fired-for-the-first-time-the-engine-was-working-perfectly-638</link>
      <guid>https://forem.com/mixa_dev/my-alert-system-fired-for-the-first-time-the-engine-was-working-perfectly-638</guid>
      <description>&lt;p&gt;I built an alert engine for my own product last week. Yesterday afternoon, it fired for the first time.&lt;/p&gt;

&lt;p&gt;This is the story of what happened, why "no signal" turned out to be the correct answer, and why I'd build the same system again tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alert
&lt;/h2&gt;

&lt;p&gt;It was 16:57 on a Sunday afternoon when my phone buzzed. The subject line read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨 LeadEdge: No signal for 29.7 hours&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The body was unambiguous:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The LeadEdge signal engine has not fired in 29.7 hours.&lt;br&gt;
This is during US market hours when signal frequency should be high.&lt;br&gt;
Last signal: sig_e263b43b2bc351fa at 2026-05-16T09:13:25.737Z&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For context: LeadEdge is a cross-exchange signal API that detects price moves on Binance Futures ETH and flags them as predictive signals for Coinbase Spot. The engine normally fires around 14 signals per day. 29.7 hours of silence wasn't normal.&lt;/p&gt;

&lt;p&gt;I was outside, ten minutes from my laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Panic
&lt;/h2&gt;

&lt;p&gt;I'd deployed major changes that morning — adding first-tick lag tracking to the signal detector. A non-trivial refactor that introduced new code paths into the price update handler.&lt;/p&gt;

&lt;p&gt;The first thought was the obvious one: I broke it.&lt;/p&gt;

&lt;p&gt;The second thought was worse: it's been silent for 30 hours, not 5. The break happened &lt;em&gt;before&lt;/em&gt; my deploy.&lt;/p&gt;

&lt;p&gt;Either way, the engine had stopped producing signals — the product I was actively pitching to potential customers via DM at that exact moment. Not great.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Diagnostic
&lt;/h2&gt;

&lt;p&gt;I opened Railway logs first. What I expected to find: stack traces, uncaught exceptions, restart loops.&lt;/p&gt;

&lt;p&gt;What I actually found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[HEARTBEAT] Uptime: 95min | Memory: 15MB
[STORAGE] Total inserted: 26,481 | Errors: 0 | Buffer: 12
[STORAGE] Total inserted: 26,708 | Errors: 0 | Buffer: 4
[WS] Clients: 0 (free=0, pro=0) | Signals: 0 | Outcomes: 0 | Auth rejections: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean. The storage layer was inserting tens of thousands of price updates with zero errors. WebSocket server was running. Memory was stable. No exceptions in the logs.&lt;/p&gt;

&lt;p&gt;But the most suspicious line: &lt;code&gt;Signals: 0 | Outcomes: 0&lt;/code&gt;. The engine was running, storing data, but not generating signals.&lt;/p&gt;

&lt;p&gt;Time to check the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was Data Actually Flowing?
&lt;/h2&gt;

&lt;p&gt;The first hypothesis: silent WebSocket failure on Binance Futures. I'd written a &lt;a href="https://dev.to/mixa_dev/your-websocket-says-connected-but-stopped-sending-data-heres-the-bug-tcp-keepalive-cant-catch-5424"&gt;Dev.to article a few weeks back&lt;/a&gt; about exactly this failure mode — WebSocket connections that look healthy at the TCP level but stopped delivering data. It happens. It's hard to detect. It nearly killed me on a separate project once before.&lt;/p&gt;

&lt;p&gt;So I ran this query in Supabase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;market_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;last_update_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;to_timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;last_update_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;server_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 hour'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;updates_last_hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;server_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;updates_last_5min&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;price_updates&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;server_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'24 hours'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;market_type&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;market_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result killed my hypothesis:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;exchange&lt;/th&gt;
&lt;th&gt;market_type&lt;/th&gt;
&lt;th&gt;updates_last_hour&lt;/th&gt;
&lt;th&gt;updates_last_5min&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;binance&lt;/td&gt;
&lt;td&gt;futures&lt;/td&gt;
&lt;td&gt;19,764&lt;/td&gt;
&lt;td&gt;941&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bybit&lt;/td&gt;
&lt;td&gt;futures&lt;/td&gt;
&lt;td&gt;3,672&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bybit&lt;/td&gt;
&lt;td&gt;spot&lt;/td&gt;
&lt;td&gt;5,209&lt;/td&gt;
&lt;td&gt;224&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;coinbase&lt;/td&gt;
&lt;td&gt;spot&lt;/td&gt;
&lt;td&gt;4,936&lt;/td&gt;
&lt;td&gt;470&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Binance Futures was firing at ~5.5 ticks per second. All exchanges were healthy. &lt;strong&gt;The data was flowing fine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So why no signals?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reveal
&lt;/h2&gt;

&lt;p&gt;If data was flowing and the engine was running, the only remaining possibility was that no tick-to-tick price change had crossed the 0.1% threshold that defines a signal.&lt;/p&gt;

&lt;p&gt;For a signal to fire on LeadEdge, the price on Binance Futures ETH must move at least 0.1% between two consecutive ticks. At ~5 ticks per second, consecutive ticks are usually milliseconds apart. A 0.1% move tick-to-tick means a sudden jump — exactly the kind of dislocation that has predictive power for the follower exchange.&lt;/p&gt;

&lt;p&gt;But what if there were no such jumps?&lt;/p&gt;

&lt;p&gt;I ran a second query, looking at the actual tick-to-tick changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_change_pct&lt;/span&gt;&lt;span class="p"&gt;))::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_change_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_change_pct&lt;/span&gt;&lt;span class="p"&gt;))::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_change_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PERCENTILE_CONT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WITHIN&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_change_pct&lt;/span&gt;&lt;span class="p"&gt;))::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p99_change_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_change_pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ticks_over_threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_ticks&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;price_updates&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'binance'&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;market_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'futures'&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;base_asset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ETH'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;server_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'3 hours'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;price_change_pct&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was definitive:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;max_change_pct&lt;/th&gt;
&lt;th&gt;avg_change_pct&lt;/th&gt;
&lt;th&gt;p99_change_pct&lt;/th&gt;
&lt;th&gt;ticks_over_threshold&lt;/th&gt;
&lt;th&gt;total_ticks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.0597&lt;/td&gt;
&lt;td&gt;0.0011&lt;/td&gt;
&lt;td&gt;0.0071&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;18,974&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In three hours, across 18,974 tick-to-tick changes, the largest single move was 0.06%. The 99th percentile was 0.007%. The average was so small it rounds to noise.&lt;/p&gt;

&lt;p&gt;ETH had been ranging in a $4 band on a quiet Sunday afternoon. There was nothing for the engine to fire on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The engine wasn't broken. The market was just unusually calm.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;This is exactly what the 0.1% threshold is supposed to do.&lt;/p&gt;

&lt;p&gt;During pre-launch validation, I measured 90.7% follow-through on signals at that threshold across 9.4M price updates and 7 days of live data. The threshold is the product. Lowering it to "catch more signals" during quiet periods would tank the accuracy that justifies the API existing in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selectivity isn't a bug. It's the entire value proposition.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The right behavior during a calm market is silence. Customers paying for cross-exchange signals don't want noise — they want the engine to fire only on moves that historically predict the follower exchange's response. If ETH is range-bound, the engine is doing its job by not firing.&lt;/p&gt;

&lt;p&gt;The wrong response would have been to panic-lower the threshold to "fix" the silence. That would be a self-inflicted regression: converting a working signal stream into a noise stream to satisfy a vanity metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Meta-Win
&lt;/h2&gt;

&lt;p&gt;Here's the part that made the whole experience worth writing about.&lt;/p&gt;

&lt;p&gt;The alert engine that emailed me yesterday was built two days earlier, in direct response to my own Dev.to article about WebSocket silent failures. I'd written about how TCP keepalive and process-level health checks can't catch a connection that's "alive" but no longer delivering data. The fix is application-aware monitoring — code that knows what the system is supposed to be doing, not just whether the process is running.&lt;/p&gt;

&lt;p&gt;After writing that article, I realized something uncomfortable: I had no application-aware monitoring on my own system. If LeadEdge silently failed, I'd find out from a customer email, not a system alert.&lt;/p&gt;

&lt;p&gt;So I built it. Two days later, it fired for the first time — not on a real failure, but on a market calm I'd otherwise have noticed days later, if at all.&lt;/p&gt;

&lt;p&gt;The alert was technically a false positive (the system was healthy). But it caught something I hadn't been thinking about: my own implicit assumption that "signals every few hours" was a guaranteed property of the system. It isn't. It depends on market behavior. And now I have an early-warning channel for unusual silence, calibrated to expected behavior.&lt;/p&gt;

&lt;p&gt;I'd rather be alerted falsely about a calm market than miss a real silent failure. The asymmetric cost is what makes monitoring like this worth building.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Tell Anyone Building Similar Systems
&lt;/h2&gt;

&lt;p&gt;A few things this experience reinforced:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build monitoring that knows your system's expected behavior, not just whether the process is alive.&lt;/strong&gt; TCP keepalive, container health checks, and "is the process running" pings all said LeadEdge was fine. They couldn't tell the difference between "running and healthy" and "running and silently broken." Application-aware monitoring is the only thing that catches the latter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build it before you need it.&lt;/strong&gt; I built the alert engine while LeadEdge's customer base was still small. If I'd waited until I had paying customers complaining about silence, the customers would have been the alert system. That's a bad place to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust validated parameters during unusual conditions.&lt;/strong&gt; It would have been easy to interpret "30 hours of silence" as evidence the threshold was wrong. The right answer was the opposite: 30 hours of silence on a quiet Sunday afternoon is evidence the threshold is working correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False positives during calm markets are tolerable. False negatives during real failures are not.&lt;/strong&gt; When calibrating an alert system, accept that you'll get woken up occasionally for nothing. The alternative — missing the alerts that actually matter — is much worse.&lt;/p&gt;




&lt;p&gt;The signal stream resumed firing about an hour after I started writing this. ETH had moved.&lt;/p&gt;

&lt;p&gt;If you want to inspect signals in real time, the free tier is at &lt;a href="https://leadedge.dev" rel="noopener noreferrer"&gt;leadedge.dev&lt;/a&gt;. The methodology and raw validation data are in &lt;a href="https://x.com/Mixa_Dev/status/2052090225623433530" rel="noopener noreferrer"&gt;my earlier thread&lt;/a&gt;. The original article on silent WebSocket failures (the one that prompted me to build the alert engine in the first place) is &lt;a href="https://dev.to/mixa_dev/your-websocket-says-connected-but-stopped-sending-data-heres-the-bug-tcp-keepalive-cant-catch-5424"&gt;here on Dev.to&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>postmortem</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your WebSocket says "connected" but stopped sending data. Here's the bug TCP keepalive can't catch.</title>
      <dc:creator>Mixa_Dev</dc:creator>
      <pubDate>Sat, 16 May 2026 19:23:32 +0000</pubDate>
      <link>https://forem.com/mixa_dev/your-websocket-says-connected-but-stopped-sending-data-heres-the-bug-tcp-keepalive-cant-catch-5424</link>
      <guid>https://forem.com/mixa_dev/your-websocket-says-connected-but-stopped-sending-data-heres-the-bug-tcp-keepalive-cant-catch-5424</guid>
      <description>&lt;p&gt;Two weeks ago, my crypto signal API silently failed for 22 hours.&lt;/p&gt;

&lt;p&gt;No errors. No exceptions. No crash. The service kept running, logs continued to flow, my deployment dashboard showed everything green. I only noticed when I happened to check the database and realized no new data had been written for almost a full day.&lt;/p&gt;

&lt;p&gt;The culprit? My WebSocket connection to Binance. It was "connected" — but it hadn't received a message in hours.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;silent staleness problem&lt;/strong&gt;. And TCP keepalive can't catch it.&lt;/p&gt;

&lt;p&gt;If you've ever built a system that consumes a long-lived WebSocket feed (price data, chat messages, IoT telemetry, log streams), you're vulnerable to this exact failure mode. Here's what's happening and how to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The illusion of "connected"
&lt;/h2&gt;

&lt;p&gt;When your client opens a WebSocket connection, the underlying TCP socket goes through a handshake. From then on, "connected" really means: there's an open TCP socket between you and the server, and TCP believes the route is alive.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;TCP keepalive (when enabled) sends periodic empty packets to verify the route. The OS does this for you. If the route is broken, you'll eventually get a connection-closed error.&lt;/p&gt;

&lt;p&gt;But here's what TCP can't see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the application on the other end is still pushing messages&lt;/li&gt;
&lt;li&gt;Whether a proxy or load balancer between you and the server has dropped your subscription&lt;/li&gt;
&lt;li&gt;Whether a backend bug stopped emitting events while keeping the connection open&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Your WebSocket can look perfectly healthy at the TCP layer while application data has stopped flowing entirely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my case, Binance's WebSocket gateway accepted my connection, accepted my subscriptions, and then stopped pushing ticker updates. The TCP socket was fine. The OS was fine. My code was fine. The data was gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why naive fixes don't work
&lt;/h2&gt;

&lt;p&gt;The first instinct is: "I'll just reconnect on error." But the application never errors. No exception fires. The connection is perfectly alive — there's just nothing coming through.&lt;/p&gt;

&lt;p&gt;The second instinct: "I'll add a watchdog timer that pings the server." This is closer to right but has a flaw — many services (including most exchange feeds) don't respond to client pings on data WebSockets. Your ping goes out, returns silence, and you can't distinguish "server doesn't ping back" from "server is broken."&lt;/p&gt;

&lt;p&gt;The third instinct: "I'll send a subscribe message and check for confirmation." This catches startup failures but not mid-stream failures.&lt;/p&gt;

&lt;p&gt;What actually works is much simpler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track the time of the last message received. If it exceeds a threshold, the stream is stale — regardless of what TCP thinks.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing message-level staleness detection
&lt;/h2&gt;

&lt;p&gt;Here's the pattern in Python:&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;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;

&lt;span class="n"&gt;STALENESS_TIMEOUT_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;  &lt;span class="c1"&gt;# tune to your feed's expected frequency
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StaleStreamError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;consume_stream&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="n"&gt;subscribe_message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscribe_message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

                &lt;span class="n"&gt;last_message_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;monitor_staleness&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STALENESS_TIMEOUT_SECONDS&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="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_message_at&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;STALENESS_TIMEOUT_SECONDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;StaleStreamError&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;No message for &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;s &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(threshold: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;STALENESS_TIMEOUT_SECONDS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                            &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="n"&gt;staleness_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;monitor_staleness&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;last_message_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handle_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;staleness_task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionClosed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection closed, reconnecting...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;StaleStreamError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Staleness detected: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, reconnecting...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# backoff before retry
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;define "alive" at your application level, not the OS level.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your feed might have natural quiet periods (markets close, low-traffic hours), so tune the threshold. A 60-second timeout might be too aggressive for IoT telemetry; a 5-minute timeout might be too lenient for a high-frequency ticker.&lt;/p&gt;

&lt;p&gt;A good heuristic: set your timeout to 3-5x the expected gap between messages during your slowest periods.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about exchange-provided heartbeats?
&lt;/h2&gt;

&lt;p&gt;Some WebSocket protocols include explicit heartbeats — small periodic messages that confirm both parties are alive at the application layer. Binance Futures, for example, sends a ping every few minutes; you respond with a pong.&lt;/p&gt;

&lt;p&gt;These help. But they don't solve the staleness problem on their own, because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Heartbeats might keep working while data subscription has died (different code paths on the server)&lt;/li&gt;
&lt;li&gt;Some feeds don't include heartbeats at all&lt;/li&gt;
&lt;li&gt;Even with heartbeats, you still need staleness logic for the data stream specifically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Treat heartbeats as one input, not the source of truth. Your real signal is: "Am I getting the kind of message I subscribed to?"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnect logic that doesn't make things worse
&lt;/h2&gt;

&lt;p&gt;When you detect staleness and reconnect, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exponential backoff:&lt;/strong&gt; if the server is genuinely down, don't hammer it with reconnect attempts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jitter:&lt;/strong&gt; if 1000 clients all detect staleness at the same instant (after a server outage), randomized retry intervals prevent a thundering herd&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State recovery:&lt;/strong&gt; for stateful feeds (order books, subscription channels), you might need to resync state after reconnect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting:&lt;/strong&gt; if you've had to reconnect more than N times in M minutes, something deeper is broken — page yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 22-hour lesson
&lt;/h2&gt;

&lt;p&gt;The bug that hit me wasn't subtle — it's a known failure mode in long-lived streaming systems. But I'd built my service assuming "WebSocket connected = data flowing," and that assumption silently broke when the assumption became false.&lt;/p&gt;

&lt;p&gt;What fixed it for good:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Message-level staleness detection&lt;/strong&gt; (the pattern above)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External health monitoring&lt;/strong&gt; — a small endpoint that returns &lt;code&gt;last_signal_age_seconds&lt;/code&gt; so UptimeRobot can alert me when it crosses a threshold&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application-level alerting&lt;/strong&gt; — a separate cron that emails me if no events fire for N hours during peak hours&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're consuming a long-lived WebSocket today and you don't have all three of these, you're vulnerable to the same silent failure. The fix is not expensive. The bug, when it hits, is.&lt;/p&gt;




&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;a href="https://leadedge.dev" rel="noopener noreferrer"&gt;LeadEdge&lt;/a&gt; — a cross-exchange crypto signal API for trading bots. The WebSocket consumer pattern above ships in our open-source &lt;a href="https://github.com/mihalismacura7-blip/leadedge-examples" rel="noopener noreferrer"&gt;integration examples&lt;/a&gt; as a drop-in for anyone building on top of similar streaming feeds.&lt;/p&gt;

&lt;p&gt;The full validation methodology with 9.4M live price updates and 90.7% follow-through on ETH cross-exchange signals is documented &lt;a href="https://leadedge.dev/blog/validation" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>phyton</category>
      <category>websocket</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
