<?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: EmilyL</title>
    <description>The latest articles on DEV Community by EmilyL (@kaihang_ho_2ad23569cdb965).</description>
    <link>https://dev.to/kaihang_ho_2ad23569cdb965</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%2F3911216%2F2c99c763-1332-402d-bc8f-f3e5f7b38306.png</url>
      <title>DEV Community: EmilyL</title>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaihang_ho_2ad23569cdb965"/>
    <language>en</language>
    <item>
      <title>Stock API Minute‑to‑Daily Bar Aggregation: How We Solved Timezone Offset Issues in Our Trading Data Pipeline</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Thu, 13 Aug 2026 02:57:28 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/stock-api-minute-to-daily-bar-aggregation-how-we-solved-timezone-offset-issues-in-our-trading-data-om2</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/stock-api-minute-to-daily-bar-aggregation-how-we-solved-timezone-offset-issues-in-our-trading-data-om2</guid>
      <description>&lt;p&gt;Hey devs! Today I want to share a data engineering war story that will resonate with anyone who has ever built a financial data pipeline. Our team supports a group of cross‑border quantitative traders who analyze US equities. To keep infrastructure costs lean, we fetch minute‑level OHLC data from a stock API and aggregate it into daily bars ourselves. Everything was fine until one Monday morning when a strategist asked, “Why does the daily open for AAPL on July 1 not match the exchange?” That question kicked off a deep dive into exchange timezones, daylight saving rules, and trading calendars.&lt;/p&gt;

&lt;h4&gt;
  
  
  Scenario: Building a Cost‑Efficient Daily Bar Factory
&lt;/h4&gt;

&lt;p&gt;We work with independent investors and small trading desks who operate across multiple markets. Paying for premium daily bar feeds for every region would eat up most of their research budget. So we architected an internal data service that consumes an affordable stock API for minute data and produces daily bars on‑the‑fly. The design is simple: ingest minute bars, group by date, compute OHLC. But that simplicity hides a critical assumption—that the date attached to each minute bar already belongs to the correct trading session.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Core Pain: UTC Grouping Splits Trading Sessions
&lt;/h4&gt;

&lt;p&gt;The stock API we use returns timestamps in UTC. For US stocks, trading happens in Eastern Time. In daylight saving, the market opens at 09:30 ET (13:30 UTC) and closes at 16:00 ET (20:00 UTC). If we group by the UTC date, the bars between 19:00 and 20:00 UTC belong to the &lt;em&gt;next&lt;/em&gt; UTC day. The result: a single trading day gets split across two daily bars. Here’s the simple illustration that made everything click for us:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time Type&lt;/th&gt;
&lt;th&gt;Corresponding Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Eastern Trading Time&lt;/td&gt;
&lt;td&gt;2026-07-01 09:30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTC Time&lt;/td&gt;
&lt;td&gt;2026-07-01 13:30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you see this, it’s obvious. But in a large pipeline that processes hundreds of symbols, the error manifests as sporadic opening‑price jumps that are maddeningly hard to trace.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution: A Time‑Normalization Layer Before Any Aggregation
&lt;/h4&gt;

&lt;p&gt;We refactored the pipeline to treat time conversion as a mandatory preprocessing step. No piece of code that computes OHLC ever sees a raw UTC timestamp. The flow now looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse the original timestamp from the API response.&lt;/li&gt;
&lt;li&gt;Load the target exchange timezone from a configuration map (for US stocks, &lt;code&gt;America/New_York&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Use Python’s &lt;code&gt;zoneinfo&lt;/code&gt; to convert to local time, fully accounting for DST.&lt;/li&gt;
&lt;li&gt;Assign a trading date based on the converted timestamp and session rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core conversion logic is self‑contained:&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;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;

&lt;span class="n"&gt;utc_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-07-01 13:30:00&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:%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;utc_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utc_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UTC&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;market_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utc_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/New_York&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet runs on every batch load, guaranteeing that all minute bars are aligned to the exchange clock before aggregation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Extending the Solution: Trading Sessions and Calendars
&lt;/h4&gt;

&lt;p&gt;Timezone conversion alone isn’t enough. Many APIs include pre‑market and after‑hours trades. If those get mixed into the daily bar, your technical indicators will quietly degrade. We therefore added a session filter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only minute bars between 09:30 and 16:00 Eastern are used for the standard daily bar.&lt;/li&gt;
&lt;li&gt;Extended‑hours data is routed to a separate analytics store.&lt;/li&gt;
&lt;li&gt;A trading calendar service dynamically adjusts for half‑days and holidays, so the system never assumes a fixed bar count.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Real‑Time Streaming Consistency
&lt;/h4&gt;

&lt;p&gt;For live trading dashboards, we ingest real‑time ticks through a WebSocket. To keep real‑time bars identical to historical ones, the tick processor reuses the same time‑normalization module. We use a low‑latency, budget‑friendly feed from AllTick for our US equity streams, and the integration was seamless because we had already solved the time problem generically.&lt;/p&gt;

&lt;p&gt;Here is our real‑time tick handler. Note how time conversion is the very first operation:&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;websocket&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;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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="n"&gt;trade_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tradeTime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trade_time&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:%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;market_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/New_York&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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="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="n"&gt;market_time&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://api.alltick.co/stock/websocket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Lessons Learned and Practical Tips
&lt;/h4&gt;

&lt;p&gt;Throughout this project, we’ve built a small checklist that might save you some pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalize early.&lt;/strong&gt; If you consume data from multiple stock APIs, agree on a single timezone standard before any merge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve raw timestamps.&lt;/strong&gt; Store them as a debug column. When an analyst questions a daily bar, the raw value is your audit trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never hard‑code bar counts.&lt;/strong&gt; Markets have early closes. Use a calendar to determine expected bar counts dynamically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encapsulate time logic.&lt;/strong&gt; A shared time‑conversion module used by batch and real‑time paths eliminates whole classes of bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;What started as a minor discrepancy in a daily bar turned into a comprehensive refinement of our entire data architecture. The fix wasn’t in the aggregation math; it was in the invisible timestamp semantics that preceded it. For any developer building financial tools on top of a stock API, my honest advice is to obsess over timezone correctness early. Once your temporal foundation is solid, the candlesticks you generate will finally reflect the true market narrative, and your traders can focus on strategy instead of data forensics.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxlfmeshdnk23tapcot6.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxlfmeshdnk23tapcot6.jpeg" alt=" " width="644" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Tutorial: Fix US Stock API Candlestick Gaps with Session-Aware Aggregation</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Wed, 12 Aug 2026 06:02:35 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/tutorial-fix-us-stock-api-candlestick-gaps-with-session-aware-aggregation-17n5</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/tutorial-fix-us-stock-api-candlestick-gaps-with-session-aware-aggregation-17n5</guid>
      <description>&lt;p&gt;Have you ever built a candlestick chart for US equities, only to find that the pre-market and after-hours segments look like a broken zipper? We’ve been there while powering financial bloggers’ data dashboards. The good news is that the issue rarely lies in your frontend library—it’s almost always a data modeling problem. In this tutorial, we’ll share how we re-architected our US stock market API pipeline to produce continuous candlestick charts across all trading sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding Why Extended Hours Create Visual Breaks&lt;/strong&gt;&lt;br&gt;
US stocks trade outside the 09:30–16:00 ET regular window, with official pre-market and after-hours sessions. These sessions contain real trades, but at drastically lower frequencies. A typical implementation that chops the day into fixed 5-minute windows will encounter many intervals with zero trades during extended hours. If your code simply skips those intervals, the time series compresses and the chart displays a price jump. The fix is to stop treating all ticks equally and start respecting the market’s session structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Force Time Consistency in Your Data Pipeline&lt;/strong&gt;&lt;br&gt;
We learned early that not all US stock APIs speak the same time language. Some return UTC, others Eastern Time. To avoid candlestick shifts, we normalize every incoming tick to UTC at the ingestion layer. We retain the original exchange timestamp in a separate field for debugging. When rendering a chart for end users, we convert back to Eastern Time. This one practice eliminates entire categories of offset bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Label Every Tick with Its Trading Session&lt;/strong&gt;&lt;br&gt;
We enrich each tick with a session identifier based on its Eastern Time timestamp:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trading Session&lt;/th&gt;
&lt;th&gt;Time Range (ET)&lt;/th&gt;
&lt;th&gt;Handling Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pre-Market&lt;/td&gt;
&lt;td&gt;Before 09:30&lt;/td&gt;
&lt;td&gt;Recorded separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regular Trading&lt;/td&gt;
&lt;td&gt;09:30–16:00&lt;/td&gt;
&lt;td&gt;Normal aggregation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After-Hours&lt;/td&gt;
&lt;td&gt;After 16:00&lt;/td&gt;
&lt;td&gt;Processed independently&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With this tagging, your aggregation logic can branch. Need a pure regular-session view? Filter by the “Regular Trading” tag. Building a full-day continuous chart? Aggregate each session separately and then merge by timestamp. The chart’s time axis stays linear because no interval is discarded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Aggregate Raw Ticks Instead of Using Pre-Built Candles&lt;/strong&gt;&lt;br&gt;
In production, we avoid consuming pre-aggregated candlestick data from any API. Instead, we stream raw tick data over WebSocket. By tapping a service like AllTick API, we receive real-time US stock trades with precise timestamps, and we perform the candlestick construction in our own code.&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;websocket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Parse incoming market data
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;loads&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="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# Stock ticker
&lt;/span&gt;    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# Latest trade price
&lt;/span&gt;    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Trade timestamp
&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Establish a WebSocket connection
&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://api.alltick.co/stock/websocket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Keep listening for new trades
&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the aggregation loop, we check the timestamp against the session boundaries, place the trade into the appropriate candlestick bucket, and emit completed candles once the bucket’s time is up. This gives us complete command over how pre-market, regular, and after-hours candles merge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Define the Merge Rules Upfront&lt;/strong&gt;&lt;br&gt;
Before showing charts to your audience, nail down the business rules. Will your daily bar incorporate after-hours prices? Do 5-minute charts during the pre-market appear as standalone segments or blend into the regular session? We document these choices with the content creators we support, so every chart they publish aligns with their analytical narrative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: Charts Your Users Can Trust&lt;/strong&gt;&lt;br&gt;
Since implementing this pipeline, the financial writers we work with have stopped receiving “why does this chart look broken?” replies. Their content is more authoritative because the underlying data respects actual market structure. If you’re building a US stock chart application, give session-aware aggregation a try—you’ll clean up those extended-hours artifacts for good.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe9d57lhq0kzxs0pl58ee.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe9d57lhq0kzxs0pl58ee.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Multi-Asset WebSocket Market Data APIs for Stocks, Forex, Crypto &amp; Commodities: A 2026 Technical Comparison with AllTick</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Thu, 06 Aug 2026 06:44:51 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/multi-asset-websocket-market-data-apis-for-stocks-forex-crypto-commodities-a-2026-technical-3nig</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/multi-asset-websocket-market-data-apis-for-stocks-forex-crypto-commodities-a-2026-technical-3nig</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Engineers building real-time dashboards, algorithmic trading systems, or market-screening tools frequently hit the same pain points: fragmented APIs that force you to stitch together WebSocket feeds from disparate providers, inconsistent data schemas across asset classes, opaque rate‑limit models that cripple backtesting, and the operational burden of managing multiple authentication tokens and connection life‑cycles. In 2026, the landscape still demands a careful evaluation of latency, coverage, and developer ergonomics before committing to a market data backbone.&lt;/p&gt;

&lt;p&gt;This article provides a technical comparison of three public APIs — AllTick, Finnhub, and Binance — with a focus on their real‑time WebSocket capabilities and REST‑based historical data retrieval. AllTick serves as the primary implementation reference because it offers native multi‑asset coverage under a single API contract, making it a representative example for workflows that span equities, forex, crypto, and commodities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selection Criteria
&lt;/h2&gt;

&lt;p&gt;Three core benchmarks guide the evaluation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real‑Time Data Delivery &amp;amp; Latency&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
How quickly does a tick travel from the exchange to the subscriber? Includes WebSocket protocol efficiency, geo‑proximity of gateway clusters, and observed end‑to‑end latency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asset Class Coverage &amp;amp; Data Granularity&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The breadth of instrument types (stocks, forex pairs, crypto, commodities) and the finest available resolution (true tick‑by‑tick vs. aggregated 1‑minute bars).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Integration Effort &amp;amp; Developer Experience&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Onboarding friction: authentication model, SDK availability, WebSocket subscription logic, rate‑limit transparency, and historical data retrieval mechanics.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Comparative Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mini‑Reviews
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AllTick&lt;/strong&gt; – A unified market data API delivering low‑latency WebSocket streams and REST endpoints for equities, forex, crypto, and commodities, designed to reduce the number of vendor integrations in multi‑asset applications.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finnhub&lt;/strong&gt; – A developer‑friendly API with strong US equity fundamentals and a generous free tier, though forex and crypto feeds are comparatively light and heavily rate‑limited.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binance API&lt;/strong&gt; – The de‑facto crypto‑native data pipe, offering exhaustive tick‑level streams, deep historical order book snapshots, and virtually unrestricted public data access, but scoped exclusively to digital assets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Comparison Matrix
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;AllTick&lt;/th&gt;
&lt;th&gt;Finnhub&lt;/th&gt;
&lt;th&gt;Binance API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free‑Tier Rate Limits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30 REST requests/min, 1 WebSocket connection, up to 10 symbols&lt;/td&gt;
&lt;td&gt;60 REST calls/min, 1 WebSocket connection, 50 symbols (US equities only on WS free)&lt;/td&gt;
&lt;td&gt;Public market data: no strict request caps; up to 5 WebSocket connections, 200 streams each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Real‑Time Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Typically &amp;lt;100 ms (Asian gateway)&lt;/td&gt;
&lt;td&gt;100‑200 ms (US equities via Finnhub WS)&lt;/td&gt;
&lt;td&gt;&amp;lt;100 ms (Binance cloud; edge clusters globally)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Granularity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tick, 1m, 5m, 15m, 30m, 1h, 4h, daily&lt;/td&gt;
&lt;td&gt;1m, 5m, 15m, 30m, 1h, daily (tick only for US stocks on paid plans)&lt;/td&gt;
&lt;td&gt;Tick (trade/aggTrade), 1m, 3m, 5m, …, daily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Supported Protocols&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;REST + WebSocket&lt;/td&gt;
&lt;td&gt;REST + WebSocket&lt;/td&gt;
&lt;td&gt;REST + WebSocket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Historical Data Depth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Up to 10 years (stocks), 5 years (forex), full exchange history (crypto); free tier includes recent 12 months&lt;/td&gt;
&lt;td&gt;1 year for free tier; extended history on paid plans&lt;/td&gt;
&lt;td&gt;Full exchange history (e.g., Binance Spot since 2017)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ideal Use Cases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi‑asset dashboards, cross‑market arbitrage scanners, brokerage back‑offices&lt;/td&gt;
&lt;td&gt;US stock sentiment analysis, lightweight portfolio tracking&lt;/td&gt;
&lt;td&gt;Crypto trading bots, deep order‑book analytics, DeFi oracles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;AllTick data in the matrix reflects the standard public plan; enterprise tiers relax rate limits and extend connectivity options.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Guide
&lt;/h2&gt;

&lt;p&gt;The following examples use the AllTick API to demonstrate typical market data workflows. All code is production‑ready Python 3.10+ and relies only on standard libraries plus &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;websocket-client&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt; – Every request must include the API key in the header &lt;code&gt;X-API-Key&lt;/code&gt;. Free keys are obtainable from the AllTick developer portal.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. REST API – Fetch Candlestick (K‑Line) Data
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/klines&lt;/code&gt; endpoint returns OHLCV bars for a given instrument and interval. Parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;code&lt;/code&gt; – Instrument identifier (e.g., &lt;code&gt;"AAPL.US"&lt;/code&gt;, &lt;code&gt;"EUR/USD"&lt;/code&gt;, &lt;code&gt;"BTC/USDT"&lt;/code&gt;, &lt;code&gt;"XAU/USD"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kline_type&lt;/code&gt; – Resolution: &lt;code&gt;"1m"&lt;/code&gt;, &lt;code&gt;"5m"&lt;/code&gt;, &lt;code&gt;"15m"&lt;/code&gt;, &lt;code&gt;"30m"&lt;/code&gt;, &lt;code&gt;"1h"&lt;/code&gt;, &lt;code&gt;"4h"&lt;/code&gt;, &lt;code&gt;"1d"&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; – Number of bars to return (max 1000 per call).
&lt;/li&gt;
&lt;/ul&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_ALLTICK_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.alltick.io/v1&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;fetch_klines&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kline_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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;url&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/klines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;params&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;code&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kline_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;resp&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&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="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;data&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;code&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;0&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&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;msg&lt;/span&gt;&lt;span class="sh"&gt;'&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;bar&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&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;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; O:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;o&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; H:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; L:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;l&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; C:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; V:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v&lt;/span&gt;&lt;span class="sh"&gt;'&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="c1"&gt;# Example: last 10 daily bars for Apple Inc.
&lt;/span&gt;&lt;span class="nf"&gt;fetch_klines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL.US&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;1d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Workflow note&lt;/strong&gt; – The response envelope always contains &lt;code&gt;"code":0&lt;/code&gt; on success, an array of OHLCV objects under &lt;code&gt;"data"&lt;/code&gt;, and an optional &lt;code&gt;"total"&lt;/code&gt; field when a time range is queried (see historical retrieval). The timestamp &lt;code&gt;t&lt;/code&gt; is epoch milliseconds in UTC.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. WebSocket – Real‑Time Tick Data
&lt;/h3&gt;

&lt;p&gt;AllTick’s WebSocket gateway supports concurrent subscription to multiple instruments across asset classes. A single connection can carry equity quotes, forex prices, crypto trades, and commodity ticks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection &amp;amp; authentication&lt;/strong&gt; – Pass the API key as a query parameter. The gateway returns a heartbeat every 30 seconds; clients should implement a reconnection back‑off.&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;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;websocket&lt;/span&gt;

&lt;span class="n"&gt;WS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://ws.alltick.io/stream&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;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tick&lt;/span&gt; &lt;span class="o"&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;loads&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="c1"&gt;# Filter out heartbeats
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tick&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;type&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tick&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  price=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tick&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  vol=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tick&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="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;def&lt;/span&gt; &lt;span class="nf"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;(&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;error&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;WebSocket error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&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;def&lt;/span&gt; &lt;span class="nf"&gt;on_close&lt;/span&gt;&lt;span class="p"&gt;(&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;close_status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;close_msg&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 in 5s...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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="nf"&gt;start_stream&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;on_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Subscribe to multiple instruments
&lt;/span&gt;    &lt;span class="n"&gt;subscribe_msg&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;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subscribe&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;symbols&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL.US&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;EUR/USD&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;BTC/USDT&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;XAU/USD&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;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_msg&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;Subscribed to real-time ticks&lt;/span&gt;&lt;span class="sh"&gt;"&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;start_stream&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;WS_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&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;on_open&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_close&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_close&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Run forever with automatic ping/pong (websocket-client handles ping)
&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ping_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ping_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="nf"&gt;start_stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tick object structure&lt;/strong&gt; – Each tick message includes &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;volume&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt; (epoch ms), and an optional &lt;code&gt;bid/ask&lt;/code&gt; spread for forex/commodities. The gateway guarantees ordered delivery within a symbol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture decision&lt;/strong&gt; – Opening a single WebSocket with multi‑symbol subscription reduces the number of file descriptors and simplifies application‑level reconnection logic compared to one connection per symbol (the pattern required by many legacy APIs). AllTick enforces a maximum of 10 symbols on the free tier; paid plans lift this limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Historical Data Retrieval
&lt;/h3&gt;

&lt;p&gt;For backtesting or down‑sampling, you often need large chunks of archived data. The &lt;code&gt;/history/kline&lt;/code&gt; REST endpoint accepts a time window and returns paginated results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameters&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;code&lt;/code&gt;, &lt;code&gt;kline_type&lt;/code&gt; – same as before.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start_time&lt;/code&gt; / &lt;code&gt;end_time&lt;/code&gt; – epoch milliseconds in UTC.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;limit&lt;/code&gt; – batch size (max 1000). If the total number of bars in the window exceeds &lt;code&gt;limit&lt;/code&gt;, the response includes a &lt;code&gt;total&lt;/code&gt; field and you must paginate using the last returned timestamp as the new &lt;code&gt;start_time&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&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;fetch_historical_klines&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kline_type&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;start_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/history/kline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;all_bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;start_ms&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;end_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;params&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;code&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kline_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;resp&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&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="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&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;0&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;msg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;all_bars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Set next start to timestamp of last received bar + 1 ms to avoid duplicates
&lt;/span&gt;        &lt;span class="n"&gt;start_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;batch&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&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="c1"&gt;# Respect rate limits: free tier allows 30 req/min
&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;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.1&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;all_bars&lt;/span&gt;

&lt;span class="c1"&gt;# Example: 1-minute bars for EUR/USD from August 1 to August 5, 2026
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&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;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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="n"&gt;end&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&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;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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="n"&gt;bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_historical_klines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EUR/USD&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;1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&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;Retrieved &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;bars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 1m bars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Error handling&lt;/strong&gt; – The function pauses 2.1 seconds between pages to stay within the 30‑request‑per‑minute window. For production, parse the &lt;code&gt;X-RateLimit-Remaining&lt;/code&gt; header when available and implement an adaptive wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow advantage&lt;/strong&gt; – The same endpoint serves stocks, forex, crypto, and commodities, and the response schema remains identical. This uniformity lets you reuse pagination logic across all asset types with zero code changes.&lt;/p&gt;

&lt;p&gt;API Docs：&lt;a href="https://apis.alltick.co/" rel="noopener noreferrer"&gt;https://apis.alltick.co/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub：&lt;a href="https://github.com/alltick/alltick-realtime-forex-crypto-stock-tick-finance-websocket-api" rel="noopener noreferrer"&gt;https://github.com/alltick/alltick-realtime-forex-crypto-stock-tick-finance-websocket-api&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ai</category>
    </item>
    <item>
      <title>Reconstructing a Cryptocurrency Order Book from Real-Time API Incremental Updates — A Practical Guide</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Thu, 06 Aug 2026 03:02:33 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/reconstructing-a-cryptocurrency-order-book-from-real-time-api-incremental-updates-a-practical-2oci</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/reconstructing-a-cryptocurrency-order-book-from-real-time-api-incremental-updates-a-practical-2oci</guid>
      <description>&lt;p&gt;When we started building out our crypto trading infrastructure, we quickly ran into a challenge that sounds simple but is actually a deep engineering problem: turning a firehose of incremental order book updates from a cryptocurrency API into a continuously accurate local representation of the market. This post is the guide we wish we’d had back then, written from the perspective of a team of independent high-frequency traders. We’ll cover the requirements, the data pain points, the specific product-level solutions we built, and how it all fits into real industry applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Are We Solving?
&lt;/h2&gt;

&lt;p&gt;We need a local order book that accurately reflects the exchange’s live state. Every strategy — from quoting to statistical arbitrage to liquidity analysis — depends on this ground truth. But the API doesn’t send us a full picture; it sends small incremental patches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Pain Points: Understanding What You’re Actually Receiving
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Incremental Updates Are Instructions, Not States
&lt;/h3&gt;

&lt;p&gt;A full order book contains every active bid and ask level with their respective quantities. However, pushing the entire book on each change would be prohibitively bandwidth-heavy. Instead, cryptocurrency APIs use incremental feeds that deliver only the modified levels. You’ll see something like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Quantity Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Buy&lt;/td&gt;
&lt;td&gt;65000&lt;/td&gt;
&lt;td&gt;+0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sell&lt;/td&gt;
&lt;td&gt;65010&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think of this as a log entry: “Apply this delta.” You cannot interpret it in isolation; you must maintain your own state and apply each delta in sequence. If you treat each delta as an independent event, the order of application can get scrambled, and your local book will drift from reality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequence Numbers Are Your Only Safeguard
&lt;/h3&gt;

&lt;p&gt;Network transport means messages can arrive out of order. A later message might reach your server before an earlier one. If you apply them by arrival time, the state becomes logically corrupted. Our solution is strict sequence-number discipline. The API provides a &lt;code&gt;sequence&lt;/code&gt; or &lt;code&gt;updateId&lt;/code&gt; with every message. Our rule:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get a full snapshot and record its sequence number.&lt;/li&gt;
&lt;li&gt;Buffer all subsequent deltas.&lt;/li&gt;
&lt;li&gt;Only apply deltas whose sequence number is exactly one greater than our current state.&lt;/li&gt;
&lt;li&gt;If a sequence gap appears, discard the local state and re-fetch a fresh snapshot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures we never build on a broken foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product Functionality: Designing the Local Book and Connection
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use a Dictionary, Not a List
&lt;/h3&gt;

&lt;p&gt;Early on, we used arrays for price levels. As the number of levels grew, update performance tanked. Now we use dictionaries (maps) keyed by price:&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;order_book&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;bids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;65000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;64999&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;65001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;65002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.1&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;On each delta: if new quantity &amp;gt; 0, set the price key; if quantity == 0, delete the key. This structure makes getting best bid/ask and calculating depth extremely fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSocket for Low-Latency Feeds
&lt;/h3&gt;

&lt;p&gt;HTTP polling introduces unacceptable latency for order book updates. We always use WebSocket connections. While integrating one feed, we based our initial handler on the AllTick API WebSocket market data pattern. The core processing loop is:&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;websocket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;order_book&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;bids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asks&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_order_book&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&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;bids&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;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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;if&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&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;asks&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;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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;if&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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;if&lt;/span&gt; &lt;span class="n"&gt;data&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;symbol&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;update_order_book&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://apis.alltick.co/websocket-api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the foundation. Wrap it with sequence validation, heartbeat checks, and automatic snapshot recovery on disconnect for a production-grade system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Industry Applications: Where Accurate Depth Matters
&lt;/h2&gt;

&lt;p&gt;We use this book for market making, arbitrage signals, and real-time liquidity monitoring. A small consistent error in depth data can gradually erode profitability. Two additional notes from our production experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After a WebSocket disconnect, the local book is outdated. Reconnect logic must fetch a fresh snapshot first, then resume deltas.&lt;/li&gt;
&lt;li&gt;Price precision: avoid floating-point keys. Convert all prices to integers based on the tick size to prevent matching errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Accurate order book reconstruction is not just about receiving data; it’s about synchronizing a distributed state. Get this right, and your trading strategies have a solid foundation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fag5hjrju5f82vb9zorio.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fag5hjrju5f82vb9zorio.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Reliable XAUUSD Tick Pipeline with a Precious Metals API: Our Battle with Daylight Saving Time</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Wed, 05 Aug 2026 03:31:16 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/building-a-reliable-xauusd-tick-pipeline-with-a-precious-metals-api-our-battle-with-daylight-3bed</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/building-a-reliable-xauusd-tick-pipeline-with-a-precious-metals-api-our-battle-with-daylight-3bed</guid>
      <description>&lt;h3&gt;
  
  
  What happens when your gold trading strategy suddenly breaks in summer
&lt;/h3&gt;

&lt;p&gt;It was one of those late-night debugging sessions that every cross-border fintech startup knows too well. Our team had just rolled out an internal platform that combined a precious metals API with a custom backtesting engine for XAUUSD tick data. The system performed beautifully during the winter months. Our momentum-based intraday strategies were hitting win rates and Sharpe ratios that made our investors raise their eyebrows — in a good way.&lt;/p&gt;

&lt;p&gt;Then, without any code changes, the models began to deteriorate. Signals that should have triggered right at the New York open were instead firing one hour late. Live simulations diverged from backtests in ways we couldn’t explain. As the founders, we were staring at a crisis of data integrity — and we needed to fix it before our track record suffered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unpacking the problem: when time becomes a variable
&lt;/h3&gt;

&lt;p&gt;We traced every failure back to one assumption we’d made early on: that converting timestamps from the precious metals API to UTC was as simple as adding a constant offset. The API returns data in US Eastern Time. We’d hard-coded Eastern Time as “UTC minus 5 hours.” That’s true in winter. It’s false during daylight saving time.&lt;/p&gt;

&lt;p&gt;The error is subtle but devastating at tick resolution. Here’s a concrete example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time Status&lt;/th&gt;
&lt;th&gt;US Eastern&lt;/th&gt;
&lt;th&gt;UTC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard Time&lt;/td&gt;
&lt;td&gt;09:30&lt;/td&gt;
&lt;td&gt;14:30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daylight Saving&lt;/td&gt;
&lt;td&gt;09:30&lt;/td&gt;
&lt;td&gt;13:30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A tick generated during the summer at 13:31 UTC was being wrongly classified under the 14:31 UTC minute candle, which completely reshuffled the order book snapshots our strategy relied on. Our precious metals API was feeding us perfect prices — our own code was misplacing them in time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Engineering the fix: a UTC-centric data pipeline
&lt;/h3&gt;

&lt;p&gt;We rebuilt our tick ingestion pipeline around a single principle: &lt;strong&gt;UTC is the only timezone that exists inside the system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Regardless of the source format, every timestamp is transformed into UTC at the earliest possible moment — before it enters the database, before it reaches the K-line aggregator, and certainly before any strategy code sees it. Python’s &lt;code&gt;zoneinfo&lt;/code&gt; module became our trusted ally because it dynamically resolves DST transitions based on the operating system’s timezone database.&lt;/p&gt;

&lt;p&gt;The conversion function we now use everywhere:&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;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;

&lt;span class="n"&gt;time_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-06-05 09:30:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;new_york&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/New_York&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;time_str&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:%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;local_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;new_york&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;utc_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utc&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;UTC time:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;utc_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For real-time data, we applied the identical logic in our WebSocket message handler. When we connected to a reliable market data provider such as AllTick for live XAUUSD ticks, the first thing &lt;code&gt;on_message&lt;/code&gt; does is convert the timezone:&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;websocket&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;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trade_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;tradeTime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;eastern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/New_York&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trade_time&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:%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;utc_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eastern&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;utc_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://api.alltick.co/ws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that point forward, K-line aggregation, strategy logic, and storage all operate on UTC. The rendering layer handles conversion to any display timezone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The business impact: reclaimed time and regained trust
&lt;/h3&gt;

&lt;p&gt;Before this fix, each DST transition season required two engineers to spend several days manually reviewing timestamps, correcting historical datasets, and re-running backtests. That’s a significant annual drain on a lean startup’s resources. Now, those days are fully reclaimed for feature development and alpha research.&lt;/p&gt;

&lt;p&gt;But the larger benefit is confidence. When your precious metals API delivers tick data and your strategies consume it, you must be absolutely certain that every data point sits in the correct temporal context. We learned the hard way that a one-hour offset can be more damaging than a pricing error. Fixing it gave us a foundation we can scale on — no matter how many strategies or markets we add.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudrrlr32r06zr051lmli.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudrrlr32r06zr051lmli.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>Handling Out‑of‑Order Level2 Messages from a US Stock API – A Practical Guide</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Thu, 30 Jul 2026 02:52:24 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/handling-out-of-order-level2-messages-from-a-us-stock-api-a-practical-guide-3b72</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/handling-out-of-order-level2-messages-from-a-us-stock-api-a-practical-guide-3b72</guid>
      <description>&lt;p&gt;As developers supporting quantitative trading desks, we’ve spent countless hours debugging order book inconsistencies. The root cause almost always traces back to one issue: &lt;strong&gt;message sequencing&lt;/strong&gt;. In this post, we’ll share our battle‑tested approach to processing Level2 data from a US Stock API, focusing on how we maintain a reliable order book despite network‑induced reordering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client Requirements: Zero Tolerance for State Errors
&lt;/h3&gt;

&lt;p&gt;Our primary users are professional traders and fund quant developers. They consume our Level2 feed to build real‑time liquidity models and execute automated strategies. Their non‑negotiable demand: the local order book must be a bit‑perfect replica of the exchange’s limit order book at any given moment. A single mis‑applied delta – for example, a modification applied before its corresponding add – can skew the entire price ladder and trigger incorrect trades. We learned this when a client reported a persistent spread miscalculation; after investigation, we found that out‑of‑order cancellation messages were the culprit.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pain Point: Network Jitter and Packet Reordering
&lt;/h3&gt;

&lt;p&gt;Unlike consolidated tape data, Level2 streams consist of discrete order‑state transitions – insert, update, delete. These operations are inherently dependent. When you fetch Level2 via a typical US Stock API over WebSocket, you’re at the mercy of internet routing. It’s entirely possible for a later event to arrive before an earlier one. For instance, the exchange emits:&lt;br&gt;
&lt;code&gt;Insert (seq=100) → Update (seq=101) → Delete (seq=102)&lt;/code&gt;&lt;br&gt;
But our receiver may see:&lt;br&gt;
&lt;code&gt;Update (101) → Insert (100) → Delete (102)&lt;/code&gt;&lt;br&gt;
Applying them in arrival order would cause an “order not found” error on the update, and later an invalid insert. This becomes especially frequent during high‑volatility periods when message rates exceed 10,000 per second.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sequence Numbers as the Single Source of Truth
&lt;/h3&gt;

&lt;p&gt;We quickly dismissed timestamp‑based sorting – it’s unreliable due to clock skew and low resolution. Instead, we rely on the monotonic &lt;code&gt;sequence&lt;/code&gt; field included in each Level2 message. Here’s an example payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;185.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"sequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10001&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We maintain a &lt;code&gt;last_processed_seq&lt;/code&gt; variable. If the incoming &lt;code&gt;sequence&lt;/code&gt; equals &lt;code&gt;last_processed_seq + 1&lt;/code&gt;, we apply the delta. If it’s greater, we declare a gap and trigger a full snapshot recovery. If it’s smaller, we discard it as duplicate. This logic is simple yet effective – it catches missing messages immediately, preventing silent data corruption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture: Snapshot + Incremental with Recovery
&lt;/h3&gt;

&lt;p&gt;Our production system follows a “snapshot + incremental” model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialization&lt;/strong&gt;: Fetch a complete order book snapshot (all price levels and aggregated sizes) via a REST endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming&lt;/strong&gt;: Open a WebSocket connection to receive incremental updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt;: For each delta, check sequence continuity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery&lt;/strong&gt;: On any gap, pause incremental processing, fetch a fresh snapshot, replace the local book, and reset the sequence counter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also implement a small delay buffer: when we receive a message with a sequence number slightly ahead (e.g., we expect 10003 but get 10004), we hold it for up to 50ms to see if 10003 arrives. If it does, we reorder and apply. If not, we fetch a snapshot. This reduces unnecessary full refreshes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Walkthrough – Python WebSocket Listener
&lt;/h3&gt;

&lt;p&gt;Below is the core implementation we use as a starting point. The example uses a common WebSocket endpoint (we’ve integrated with various providers; the pattern is identical). Notice the sequence validation in the callback:&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;websocket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;last_sequence&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;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;last_sequence&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;loads&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="n"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;sequence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;last_sequence&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;seq&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;last_sequence&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="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;Data gap detected – re‑sync required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;last_sequence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;data&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;symbol&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="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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://api.alltick.co/ws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production, we add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic reconnection with exponential backoff.&lt;/li&gt;
&lt;li&gt;A thread‑safe cache for out‑of‑order messages.&lt;/li&gt;
&lt;li&gt;Health checks that compare our local book against occasional snapshot hashes.&lt;/li&gt;
&lt;li&gt;Metrics to monitor gap frequency and recovery latency.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The hardest part of Level2 processing isn’t writing the update logic – it’s ensuring the update order is correct. We’ve come to treat sequence validation as our primary defense against data corruption. If you’re building any system that consumes depth‑of‑market data, invest in this foundation first. Accurate prices follow accurate sequences – never the other way around.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F47jnzp2hn1s2lj6kupet.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F47jnzp2hn1s2lj6kupet.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>Handling Cross-Market Time Alignment When Generating K-Lines from Precious Metals APIs</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Wed, 29 Jul 2026 03:11:23 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/handling-cross-market-time-alignment-when-generating-k-lines-from-precious-metals-apis-5f0a</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/handling-cross-market-time-alignment-when-generating-k-lines-from-precious-metals-apis-5f0a</guid>
      <description>&lt;p&gt;&lt;strong&gt;The Problem: K-Line Gaps That Shouldn't Exist&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I maintain a real-time market monitoring system for precious metals that I use in microstructure research. A while back, I ran into a maddening issue: the 1-minute K-lines I generated sometimes contained unexplained gaps, sudden price jumps, or volume spikes that didn't correspond to any market event. The raw tick stream? Perfectly normal. The candles? Occasionally broken.&lt;/p&gt;

&lt;p&gt;After exhaustively debugging the candle-building algorithm (it was fine), I traced the root cause to something much more fundamental: &lt;em&gt;cross-market session transitions and inconsistent time formats across data sources&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Cross-Market Hours Break Your Candles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The precious metals market is a relay race. Liquidity moves from Asia to Europe to North America, and the characteristics of the tick stream change dramatically during handovers. Tick frequency accelerates, decelerates, and sometimes briefly disappears. Meanwhile, different APIs might return timestamps in UTC, local exchange time, or even unspecified timezones. When your K-line generator uses these raw timestamps to cut candles at fixed intervals, the boundaries drift. What looks like a simple 1-minute candle becomes a mismatched aggregation of ticks that belong to different time domains.&lt;/p&gt;

&lt;p&gt;Common symptoms I've documented:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Anomaly Type&lt;/th&gt;
&lt;th&gt;Manifestation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time Interval Gaps&lt;/td&gt;
&lt;td&gt;Extended blanks appear between consecutive K-lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price Continuity Breaks&lt;/td&gt;
&lt;td&gt;A new candle opens far from the previous close&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume Irregularities&lt;/td&gt;
&lt;td&gt;Tick count surges or drops abruptly without news&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate Records&lt;/td&gt;
&lt;td&gt;Multiple ticks carry identical timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These aren't necessarily data errors. They're alignment failures that require time-aware handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Researchers Actually Need: A Consistent Temporal Backbone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As someone who often prepares datasets for academic institutions, I've learned that the primary data requirement is temporal consistency. You need every tick to be anchored to the same time standard—preferably UTC—with high precision. Only then can you enforce uniform K-line boundaries across different market zones. Without it, any subsequent analysis (volatility estimation, jump detection, intraday pattern mining) is built on a shaky foundation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My Solution: Normalize, Validate, Preserve&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I restructured my processing pipeline around three principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Normalize time to UTC on ingestion&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Regardless of how a data source timestamps its ticks, the very first step is conversion to UTC. Then I define K-line windows using strict UTC boundaries. For example, for 1-minute candles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12:00:00 – 12:00:59&lt;/li&gt;
&lt;li&gt;12:01:00 – 12:01:59
This ensures that ticks from all sessions fall into the correct bucket based on absolute time, not on the source's clock.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pre-aggregation validation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Before a tick enters a candle, I check the time delta from the previous tick and the price change. If there's a large gap, I determine whether we're in a known low-liquidity period (like mid-Asian session). If the gap is expected, I proceed normally; if not, I flag it. Price anomalies get marked but don't immediately corrupt the candle—they're held for review. This context-aware validation prevents false positives while still catching real issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retain raw tick data&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I persist every incoming tick unchanged. If any K-line ever looks suspicious, I can replay the entire aggregation from the original data. This approach guarantees reproducibility, which is essential for academic validation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Real-Time Implementation with WebSockets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For live data, WebSockets are far superior to polling REST endpoints—they preserve the natural order of ticks and avoid timing jitter. In my current stack, I consume a WebSocket feed that delivers ticks in real time. The critical part of the message handler is immediate time normalization. Here's a simplified version of what runs in production:&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;websocket&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;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Normalize timestamp to UTC right away
&lt;/span&gt;    &lt;span class="n"&gt;utc_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;timestamp&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="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&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;AllTick API&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;utc_time&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;on_open&lt;/span&gt;&lt;span class="p"&gt;(&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;subscribe&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;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subscribe&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;symbol&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;XAUUSD&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;type&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;tick&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&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&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://api.alltick.co/ws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_open&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By converting time to UTC before the tick hits the candle builder, I eliminated nearly all the phantom dislocations I used to see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distinguishing Market Lulls from Real Data Loss&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A subtle but important point: thin tick volume during session transitions is normal, not an error. If your validation logic simply counts ticks, you'll end up deleting real market data. I now use a combined rule set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check timestamp continuity&lt;/li&gt;
&lt;li&gt;Evaluate price change plausibility&lt;/li&gt;
&lt;li&gt;Assess data completeness against expected tick rate&lt;/li&gt;
&lt;li&gt;Determine the current market session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When all four dimensions align, even a slow tick stream is treated as valid. This preserves the natural microstructure of low-activity periods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Academic Value: Time Integrity as Research Infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For anyone conducting empirical market research, getting the time axis right isn't a nice-to-have—it's the minimum requirement for credible results. Once you've enforced UTC normalization, context-sensitive validation, and raw data persistence, your K-lines become a reliable substrate for everything from volatility signature plots to order flow analysis. The data source matters, but how you manage time across market boundaries is what ultimately determines whether your study stands up to scrutiny or falls apart due to hidden temporal distortions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04qxpf5k7puyz9uhl7j1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04qxpf5k7puyz9uhl7j1.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Reconstructing Historical Order Books with a Crypto API – A Step‑by‑Step Guide</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Thu, 23 Jul 2026 03:44:56 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/reconstructing-historical-order-books-with-a-crypto-api-a-step-by-step-guide-5861</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/reconstructing-historical-order-books-with-a-crypto-api-a-step-by-step-guide-5861</guid>
      <description>&lt;p&gt;We’ve all been there: you’re debugging a backtest, and the strategy made a terrible decision at 14:32:07. You check the candlestick—nothing unusual. You check the volume—normal. So &lt;em&gt;what caused the slip?&lt;/em&gt; The answer almost always lies in the order book. But how do you get the order book as it was at that exact millisecond, long after the moment has passed?&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll show you how to use a crypto API’s WebSocket stream to capture and store order‑book updates, so you can later retrieve a full snapshot for any given timestamp. We’ll share our own architecture, code snippets, and the lessons we’ve learned along the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Order‑Book Snapshot
&lt;/h3&gt;

&lt;p&gt;A snapshot is a point‑in‑time representation of all active limit orders. It consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bids&lt;/strong&gt; – buy orders with prices and quantities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asks&lt;/strong&gt; – sell orders with prices and quantities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamp&lt;/strong&gt; – when the snapshot was taken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbol&lt;/strong&gt; – the market pair.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example (BTCUSDT):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BTCUSDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1784188200000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"65000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.5"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"64990"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.8"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"asks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"65010"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"65020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this data, we can analyse depth changes preceding price moves—essential for any serious quant.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: No Direct Historical Query
&lt;/h3&gt;

&lt;p&gt;We scoured the documentation of every major crypto API. None provides a “get historical order book” method. The reason is obvious: order books mutate every few milliseconds, and storing all versions is prohibitively expensive. So we must design a system that subscribes to real‑time updates and persists them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparing Data Collection Methods
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP REST polls&lt;/td&gt;
&lt;td&gt;Low (misses intra‑interval changes)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Casual monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSocket stream&lt;/td&gt;
&lt;td&gt;High (captures every event)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Backtesting, research&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We chose WebSocket because our backtests require event‑level precision. Let’s see how to set it up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting to a Stream (Using AllTick API as an Example)
&lt;/h3&gt;

&lt;p&gt;We used a provider that offers a straightforward WebSocket endpoint—we’ll call it AllTick API. The connection code is minimal:&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;websocket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://apis.alltick.co/websocket-api/stock-websocket-interface-api/transaction-quote-subscription&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;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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="nf"&gt;print&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;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&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;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In reality, we don’t just print. We parse the message, identify whether it’s a full snapshot or an incremental update, and then feed it into our storage pipeline. We also maintain a local in‑memory copy of the order book.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage Design: Choose Your Granularity
&lt;/h3&gt;

&lt;p&gt;The volume of order‑book data is large. We recommend storing different densities for different purposes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Storage Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple trend review&lt;/td&gt;
&lt;td&gt;Full snapshot every 5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strategy backtesting&lt;/td&gt;
&lt;td&gt;Full snapshot every 200 ms + delta logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High‑frequency research&lt;/td&gt;
&lt;td&gt;All deltas, no snapshots (reconstruct later)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We personally use a hybrid: we save full snapshots every second and archive all deltas. To reconstruct a specific time, we load the nearest snapshot and replay the deltas. Always use the exchange’s timestamp—not your local time—as the authoritative ordering key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Critical Operational Checks
&lt;/h3&gt;

&lt;p&gt;Over time, we’ve compiled this checklist to keep our data clean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timestamp gap detection&lt;/strong&gt; – if the interval between two messages exceeds a threshold (say 100 ms), we assume packet loss and flag that range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconnection logic&lt;/strong&gt; – after a WebSocket disconnect, we first fetch a full snapshot (via REST) to re‑establish the baseline, then resume the delta stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message type handling&lt;/strong&gt; – we strictly separate &lt;code&gt;snapshot&lt;/code&gt; (overwrite) from &lt;code&gt;update&lt;/code&gt; (merge) to avoid double‑counting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanity filters&lt;/strong&gt; – we reject prices ≤0, quantities ≤0, and any value that deviates wildly from the current mid‑price.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These checks have drastically reduced our debugging time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why We Keep Building This Archive
&lt;/h3&gt;

&lt;p&gt;We believe that the true power of a crypto API lies not in real‑time ticks, but in the historical depth it enables us to build. When we encounter a black‑swan event, we can roll back the tape and watch how the order book evolved—which levels were defended, which were abandoned. That insight informs our model adjustments and risk management.&lt;/p&gt;

&lt;p&gt;We encourage every developer to start small—maybe just one pair, one snapshot per second—and expand over time. The data you accumulate will become your most valuable asset for strategy innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjzkbv42s572ik8gnr7yh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjzkbv42s572ik8gnr7yh.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>How to Recover Real-Time Stock Snapshots After a Trading Halt</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Wed, 22 Jul 2026 02:43:19 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/how-to-recover-real-time-stock-snapshots-after-a-trading-halt-k36</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/how-to-recover-real-time-stock-snapshots-after-a-trading-halt-k36</guid>
      <description>&lt;p&gt;When you’re building a trading bot, it’s easy to focus on signal logic and forget that market data has &lt;em&gt;state&lt;/em&gt;. Stocks get suspended, go into auctions, or resume trading after days of silence. If your system doesn’t explicitly handle these transitions, you’ll end up with corrupted snapshots and phantom signals. Today, I’ll walk through how our team solved this at a high-frequency prop shop, and give you a reusable pattern you can drop into your own pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario: The resumption that wasn’t&lt;/strong&gt;&lt;br&gt;
Imagine your strategy holds a stock that’s halted for an acquisition announcement. During the halt, your local cache shows the last trade at $50. Two weeks later, the stock reopens at $62. If your code simply updates the cache with the first new tick it receives, it may average these values, generate a false breakout, or trip a circuit breaker. We learned this the hard way when a misprocessed resumption triggered a $200k accidental unwind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why naive approaches fail&lt;/strong&gt;&lt;br&gt;
A quick comparison of common data access methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP polling&lt;/strong&gt; – You get the latest price every few seconds, but you don’t know if the stock is halted or if the API is returning a cached response. Timestamp often remains unchanged during a halt, so you can’t detect the resume event quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web scraping&lt;/strong&gt; – Unreliable for state; you’ll be guessing based on screen text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broker SDKs&lt;/strong&gt; – Sometimes include status, but it may be on a different stream or require additional parsing, adding latency and complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We needed an event-driven solution that delivers the trading status &lt;em&gt;with&lt;/em&gt; the tick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter tick-level state fields&lt;/strong&gt;&lt;br&gt;
This is where a professional real-time WebSocket API shines. With AllTick’s tick stream, every message includes a status indicator (e.g., “TRADING”, “HALTED”). That means you can build a state machine entirely within your message handler, no external calls necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation fields you must check&lt;/strong&gt;&lt;br&gt;
When a halt ends, don’t trust the first tick blindly. Verify these fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Field&lt;/th&gt;
&lt;th&gt;What It Tells You&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instrument status&lt;/td&gt;
&lt;td&gt;Must equal “TRADING”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;Must be newer than the halt start time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last price&lt;/td&gt;
&lt;td&gt;The actual post-resumption transaction price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume&lt;/td&gt;
&lt;td&gt;Should be &amp;gt; 0 to confirm real trade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bid/ask depths&lt;/td&gt;
&lt;td&gt;Must be present and non-stale&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We use a simple &lt;code&gt;last_valid_ts&lt;/code&gt; variable per symbol. Every incoming tick must have &lt;code&gt;timestamp &amp;gt; last_valid_ts&lt;/code&gt; to be accepted. This prevents old ticks from reordering and overriding fresh data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The recovery flow&lt;/strong&gt;&lt;br&gt;
Here’s the step-by-step state machine we implemented:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normal trading&lt;/strong&gt;: Continuously update snapshot cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Halt detected&lt;/strong&gt;: Freeze cache, save pre-halt snapshot as a separate object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resumption signal received&lt;/strong&gt;: Set state to &lt;code&gt;WAITING_FIRST_TICK&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First tick validation&lt;/strong&gt;: Check timestamp &amp;gt; &lt;code&gt;halt_ts&lt;/code&gt;, volume &amp;gt; 0, price ≠ null.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache unfreeze&lt;/strong&gt;: Overwrite snapshot with validated tick and notify downstream modules (K-lines, risk, etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation between cache and state keeps your data lineage clean and lets you replay events for debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code example&lt;/strong&gt;&lt;br&gt;
Here’s a minimal WebSocket subscriber. In real use, you’d expand the &lt;code&gt;on_message&lt;/code&gt; function with the state checks discussed above.&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;websocket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Real-time tick subscription endpoint
&lt;/span&gt;&lt;span class="n"&gt;api_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://apis.alltick.co/websocket-api/stock-websocket-interface-api/transaction-quote-subscription&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;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Placeholder for state validation and snapshot update
&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;update market snapshot:&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="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To productionize this, wrap the logic in a class that manages per-symbol state and timestamps, perhaps backed by Redis for persistence across restarts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical recommendations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always run a pre-validation step before sending data to your strategy. If the status isn’t “TRADING,” skip the tick or route it to a monitoring queue.&lt;/li&gt;
&lt;li&gt;Keep the pre-halt snapshot for audit; it’s invaluable for post-trade analysis.&lt;/li&gt;
&lt;li&gt;Test your recovery with historical halt/resumption events. Replay them against your handler to catch edge cases early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember: tick data is not a continuous film; it’s a series of snapshots with occasional intermissions. Respect those breaks, and your algorithms will thank you.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmee4cllxzr1odt4ohhej.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmee4cllxzr1odt4ohhej.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Real‑Time Gold Breakout Detector for Cross‑Border Portfolios — A FinTech Lead’s Workflow</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Thu, 16 Jul 2026 03:14:47 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/building-a-real-time-gold-breakout-detector-for-cross-border-portfolios-a-fintech-leads-workflow-eci</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/building-a-real-time-gold-breakout-detector-for-cross-border-portfolios-a-fintech-leads-workflow-eci</guid>
      <description>&lt;p&gt;When you build data pipelines for cross‑border investors, “close enough” doesn’t cut it. A gold breakout that fires 20 seconds late, or mislabels a tiny spike as a trend change, can ripple into hedging errors across currencies. In this tutorial, I’ll share the real‑time detection workflow we use internally, compare the data access patterns we evaluated, and show you how to implement a state‑aware breakout monitor with WebSockets.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Cross‑Border Investor’s Monitoring Nightmare
&lt;/h2&gt;

&lt;p&gt;Picture a trader juggling gold exposure in USD, EUR, and JPY. Her system polls a REST endpoint every 10 seconds. Gold shoots through resistance at 2,080, pulls back, and the alert arrives when the price is already 2,075. She hedges on stale information. We fixed this by treating the breakout as a continuous state machine fed by tick‑level data, not a discrete threshold check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polling vs Streaming: What Worked for Us
&lt;/h2&gt;

&lt;p&gt;We benchmarked three approaches before settling on a streaming architecture:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Granularity&lt;/th&gt;
&lt;th&gt;Suitability for Breakout Detection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;REST polling (5–10s)&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Snapshot only&lt;/td&gt;
&lt;td&gt;Misses intra‑interval spikes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delayed WebSocket feeds&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Batched&lt;/td&gt;
&lt;td&gt;Unreliable timestamps break state machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native tick‑by‑tick WebSocket&lt;/td&gt;
&lt;td&gt;Ultra‑low&lt;/td&gt;
&lt;td&gt;Every tick&lt;/td&gt;
&lt;td&gt;Ideal — captures the full micro‑structure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For gold, where moves accelerate around news and fixings, only the tick‑by‑tick stream gave us the temporal resolution to confidently distinguish a breakout from noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Chose AllTick API for Precious Metals Streaming
&lt;/h2&gt;

&lt;p&gt;During our comparison, we connected to multiple providers. The AllTick API stood out for its clean, unauthenticated WebSocket handshake (great for prototyping), low latency tick delivery for gold, and straightforward JSON subscription model. It became our backbone for precious metals real‑time data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Breakout Monitor
&lt;/h2&gt;

&lt;p&gt;Let’s walk through the core components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Subscribe to Gold Ticks
&lt;/h3&gt;

&lt;p&gt;We open a WebSocket connection and subscribe to &lt;code&gt;GOLD&lt;/code&gt;. The tick objects arrive with price, timestamp, and volume.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# WebSocket real-time quote subscription example
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://apis.alltick.co/websocket-api/stock-websocket-interface-api/transaction-quote-subscription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GOLD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;subscribe_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subscribe&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;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;tick&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting live gold tick listener&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Stateful Breakout Logic
&lt;/h3&gt;

&lt;p&gt;A simple condition isn’t enough. We use a multi‑stage check that considers price level, change rate, and dwell time.&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;if&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resistance_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;breakout_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Breakout Under Observation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price_change_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;breakout_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Confirmed Breakout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;breakout_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No Breakout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, we wrap this in a class that tracks consecutive ticks above resistance. If the count hits a minimum (e.g., 5) &lt;em&gt;and&lt;/em&gt; the rolling volatility expands, we promote the status. Otherwise, we suppress the alert. This dramatically cuts false positives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Enhance with Historical Ranges
&lt;/h3&gt;

&lt;p&gt;We pull historical daily candles to compute support and resistance zones. When the live price enters a zone that previously saw multiple failures or breakouts, we adjust the required confirmation time and rate thresholds dynamically. This makes the system sensitive to high‑value levels and relaxed in messy ranges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Production Hardening
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timestamp hygiene:&lt;/strong&gt; All ticks are stamped in UTC milliseconds upon arrival and ordered by sequence number. Out‑of‑order ticks trigger a resync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconnection strategy:&lt;/strong&gt; Our WebSocket client auto‑reconnects and compares the last processed timestamp with the first tick of the new stream. Any gap is logged and a REST snapshot is used to realign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heartbeat watch:&lt;/strong&gt; Missing ticks for more than a configurable window flags the status as “stale,” preventing downstream consumers from acting on dead data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways for Developers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real‑time data is not a luxury for cross‑border gold monitoring; it’s the foundation that prevents entire categories of false breakouts.&lt;/li&gt;
&lt;li&gt;A small state machine with dwell time and volatility checks outperforms complex ML models when explainability and speed matter.&lt;/li&gt;
&lt;li&gt;Invest time in stream resilience early: reconnection, ordering, and heartbeats will save you from 3‑AM wake‑up calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Link to the API we used:&lt;/strong&gt; &lt;a href="https://www.alltick.co" rel="noopener noreferrer"&gt;AllTick API documentation&lt;/a&gt; — explore the WebSocket interface that powers our gold monitor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbq7450omsy5677kybk2o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbq7450omsy5677kybk2o.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Ensuring Timestamp Continuity in Historical Stock Replay — A Quant Architect’s Cost-Efficient Playbook</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Wed, 15 Jul 2026 03:11:00 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/ensuring-timestamp-continuity-in-historical-stock-replay-a-quant-architects-cost-efficient-585n</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/ensuring-timestamp-continuity-in-historical-stock-replay-a-quant-architects-cost-efficient-585n</guid>
      <description>&lt;p&gt;I work as a strategy chief architect for quantitative hedge funds and private equity managers. My title often drifts into “profit curve optimizer” because my mandate is brutally simple: make the backtest equity curve match live trading, and do it without blowing the tech budget. In this post, I’ll share the exact timestamp hygiene protocol I use to banish replay distortions — a protocol that delivers institutional-grade reliability on a shoestring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Client’s Core Need: Reliable Time on a Lean Budget
&lt;/h2&gt;

&lt;p&gt;Quant fund managers and private equity investors don’t want another dashboard. They want &lt;strong&gt;replay veracity&lt;/strong&gt;. They need to know that if a strategy shows a 2.5 Sharpe in simulation, it won’t embarrass them with a 0.3 in production. And critically, they want this assurance without paying for atomic clocks or proprietary timestamping hardware. &lt;strong&gt;Cost-effectiveness&lt;/strong&gt; is the lens through which every engineering decision is judged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Replay Goes Wrong: Advisor-Side Pain Points
&lt;/h2&gt;

&lt;p&gt;When I first wired a stock quote WebSocket into a replay store, I assumed data completeness equaled data correctness. Reality taught me otherwise. The pipeline introduced three classes of timestamp anomalies that I now actively guard against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Out‑of‑order delivery&lt;/strong&gt;: network jitter reversing the true trade sequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Granularity clash&lt;/strong&gt;: some vendors emit second‑level stamps, others millisecond.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gap misinterpretation&lt;/strong&gt;: a market closure being replayed as an instantaneous price jump, shredding short‑term indicators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These issues don’t just annoy quants — they erode the advisor’s faith in their own research, leading to delayed launches and missed alpha windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data-Backed Solution: A Lightweight Temporal Governance Model
&lt;/h2&gt;

&lt;p&gt;I introduced a three‑tier timestamp policy that requires zero additional software cost:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time Field&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;market_time&lt;/td&gt;
&lt;td&gt;The definitive sort key for all replay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;receive_time&lt;/td&gt;
&lt;td&gt;Latency monitoring and anomaly alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;store_time&lt;/td&gt;
&lt;td&gt;Root‑cause analysis for data pipeline issues&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fast pre‑replay validator enforces three rules: timestamps must be strictly increasing (with configurable tolerance for bursts), gaps wider than a dynamic threshold are flagged, and repeated timestamps are cross‑checked with price and volume to distinguish authentic fills from duplicate packets. Across a dozen funds I’ve supported, this simple scheme has brought the backtest‑to‑live tracking error down to less than 15 bps on daily strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Upgrade: Aligning Live Capture and Replay Clocks
&lt;/h2&gt;

&lt;p&gt;The last mile was removing local server time from the equation entirely. I now ingest only the exchange‑provided timestamp from the real‑time feed. For example, when leveraging AllTick API for tick capture, I store the &lt;code&gt;timestamp&lt;/code&gt; as the golden field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# WebSocket subscription for real-time market data
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://apis.alltick.co/websocket-api/stock-websocket-interface-api/transaction-quote-subscription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;tick_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Persist using the exchange market time
&lt;/span&gt;&lt;span class="nf"&gt;save_tick&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tick_time&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&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;volume&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="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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your live and historical pipelines share the exact same time anchor, you eliminate the silent mismatch that poisons countless backtests. Top it off with sane exception handling — gap journals, fingerprint‑based deduplication, pre‑built time indices — and your replay module becomes a trusted foundation instead of a source of doubt.&lt;/p&gt;

&lt;p&gt;I’ve learned that in systematic trading, expensive hardware rarely solves what disciplined timestamp logic can fix for free. Get your time axis right, and the rest of the strategy stack suddenly behaves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2uzrkcm07e8cfeprd3ok.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2uzrkcm07e8cfeprd3ok.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Build Unbroken Candlestick Charts for US Stocks: A Three-Layer Session Tagging Approach</title>
      <dc:creator>EmilyL</dc:creator>
      <pubDate>Tue, 07 Jul 2026 06:02:08 +0000</pubDate>
      <link>https://dev.to/kaihang_ho_2ad23569cdb965/how-to-build-unbroken-candlestick-charts-for-us-stocks-a-three-layer-session-tagging-approach-ogc</link>
      <guid>https://dev.to/kaihang_ho_2ad23569cdb965/how-to-build-unbroken-candlestick-charts-for-us-stocks-a-three-layer-session-tagging-approach-ogc</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: Candlestick Gaps That Confuse Users and Algorithms
&lt;/h2&gt;

&lt;p&gt;When our team integrated US stock market data into a platform originally built for cryptocurrency traders, we ran into a persistent issue: candlestick charts exhibited ugly jumps around market open and close. Pre-market price moves would disappear from the visual timeline, and low-volume after-hours prints would create phantom volume spikes. The root cause? Most raw data feeds dump all ticks into a single undifferentiated stream, ignoring the fact that US equities trade in three distinct sessions — pre-market (4:00–9:30 ET), regular (9:30–16:00), and after-hours (16:00–20:00). Our users, many of whom are professional advisors and algo traders, demanded a chart that was visually continuous without sacrificing logical accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional Pains: When Advisors Manually Strip Session Noise
&lt;/h2&gt;

&lt;p&gt;Before we shipped our solution, we spent hours interviewing power users. A recurring complaint: they were forced to manually filter out non-regular-hours data before running technical analysis, because indicators like VWAP and RSI would be skewed by stray trades in thin markets. This manual step was error-prone and impossible to automate at scale. They needed the platform to handle session semantics transparently, leaving them to focus on strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Architecture That Made Continuity Possible
&lt;/h2&gt;

&lt;p&gt;We tackled this by designing a three-tier tick processing pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Raw Tick Ingestion&lt;/strong&gt; – Using real-time WebSocket streams (e.g., AllTick), we capture every transaction with microsecond precision. No filtering, no judgment — just collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalization &amp;amp; Session Labeling&lt;/strong&gt; – All timestamps are converted to US Eastern Time. A simple time-window function assigns each tick a &lt;code&gt;session&lt;/code&gt; label (&lt;code&gt;pre_market&lt;/code&gt;, &lt;code&gt;regular&lt;/code&gt;, &lt;code&gt;after_hours&lt;/code&gt;). Outlier filtering happens here as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session-Aware OHLC Aggregation&lt;/strong&gt; – When building K-lines, we partition ticks by session &lt;em&gt;and&lt;/em&gt; time bucket. Crucially, we never merge volumes across session boundaries, which eliminates the phantom spike problem entirely. Bars are generated only where actual trades exist — we never fabricate "filler" candles.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# WebSocket tick ingestion with session labeling downstream
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://stream.alltick.co/v1/stock/realtime&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;on_message&lt;/span&gt;&lt;span class="p"&gt;(&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;message&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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="c1"&gt;# In production, session label and aggregation logic are applied here
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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;on_open&lt;/span&gt;&lt;span class="p"&gt;(&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;sub_msg&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;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subscribe&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;channel&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;transaction_quote&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;symbols&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&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;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;sub_msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocketApp&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;on_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_open&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_open&lt;/span&gt;
&lt;span class="p"&gt;)&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;run_forever&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: a visually seamless candlestick chart where each segment knows whether it belongs to pre-market, regular, or after-hours trading. Algorithms can optionally filter by session, but the default view is free of artificial gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Upgrade: Making Session Intelligence a Platform Feature
&lt;/h2&gt;

&lt;p&gt;We subsequently wrapped this pipeline into a dedicated market-data microservice. Product teams can now request session-aware candlestick series via a single API parameter. This has significantly reduced onboarding time for new chart features and allowed our quant community to run session-specific backtests without extra data wrangling. What started as a chart glitch turned into a key differentiator.&lt;/p&gt;

</description>
      <category>data</category>
      <category>dataengineering</category>
      <category>fintech</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
