<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[real time market data api]]></title><description><![CDATA[real time market data api]]></description><link>https://tickdb.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:07:53 GMT</lastBuildDate><atom:link href="https://tickdb.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Stock API Watchlists Can Share Requests Without Hiding Stale Prices]]></title><description><![CDATA[A stock watchlist can exhaust an API budget even when every user follows the same symbols.]]></description><link>https://tickdb.hashnode.dev/stock-api-watchlists-can-share-requests-without-hiding-stale-prices</link><guid isPermaLink="true">https://tickdb.hashnode.dev/stock-api-watchlists-can-share-requests-without-hiding-stale-prices</guid><category><![CDATA[free stock api,]]></category><category><![CDATA[tickdb]]></category><category><![CDATA[marketdata]]></category><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Thu, 05 Nov 2026 14:27:44 GMT</pubDate><content:encoded><![CDATA[<h1>Stock API Watchlists Can Share Requests Without Hiding Stale Prices</h1>
<p><strong>Summary:</strong> A stock watchlist can exhaust an API budget even when every user follows the same symbols. Where data permissions allow it, batching those symbols and sharing retrieval across viewers reduces duplicate upstream requests while preserving each record's timestamp. In a hypothetical case with 20 identical 120-symbol watchlists, batches of 50, and a five-second polling interval, independent polling needs 720 requests per minute; one shared collector needs 36, before retries and other traffic.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/5b4c42ec-b448-4ee5-9b65-edb62362844a.png" alt="" style="display:block;margin:0 auto" />

<p>A stock API lets software request or receive stock-related data. In a watchlist, the application uses that interface to populate rows with prices and other available fields. The same data can support a portfolio overview or a research dashboard, but the API does not decide how often the screen should refresh or how the application should label an old observation.</p>
<p>Those decisions become visible as an app grows. If every open browser starts its own polling loop, upstream requests grow with the audience even when the requested symbols are identical. A shared collector can remove that duplication. It must also preserve data age: serving an old record quickly does not make the record current.</p>
<h2>Count upstream requests before choosing a refresh interval</h2>
<p>For polling, count the batches needed for one refresh cycle, then multiply by the cycles per minute. If 120 distinct symbols fit into batches of at most 50, each cycle needs three requests. A five-second interval gives 12 cycles per minute, or 36 requests per minute for one collector.</p>
<p>Twenty independent collectors doing the same work require 720 requests per minute. One collector serving those same viewers still needs 36 upstream requests per minute under these assumptions. The application must still deliver updates to its clients; sharing retrieval does not remove that downstream work.</p>
<p>The batch size has a concrete implementation example: <a href="https://tickdb.ai/">TickDB</a> provides a Ticker Snapshot endpoint that accepts up to 50 symbols per request. Its response includes the symbol, last traded price, and a timestamp; other market-data fields are conditional on the product, session, and availability. This makes it a useful example for a watchlist adapter, provided the selected plan and data permissions fit the application.</p>
<p>The calculation below is an offline planning tool. It uses synthetic identifiers, makes no API calls, and assumes all lists belong to one permitted sharing group with the same feed, fields, and refresh cadence.</p>
<pre><code class="language-python">from math import ceil, isfinite

def request_budget(watchlists, batch_size, interval_s):
    if type(batch_size) is not int or batch_size &lt;= 0:
        raise ValueError("batch_size must be a positive integer")
    if not isfinite(interval_s) or interval_s &lt;= 0:
        raise ValueError("interval_s must be positive and finite")
    lists = [set(symbols) for symbols in watchlists]
    union = set().union(*lists)
    cycles_per_minute = 60 / interval_s
    independent = sum(ceil(len(s) / batch_size) for s in lists)
    shared = ceil(len(union) / batch_size)
    return {
        "independent_rpm": independent * cycles_per_minute,
        "shared_rpm": shared * cycles_per_minute,
    }

symbols = [f"DEMO_{i:03d}" for i in range(120)]
print(request_budget([symbols] * 20, 50, 5))
# {'independent_rpm': 720.0, 'shared_rpm': 36.0}
</code></pre>
<p>The function deduplicates symbols within each list and across the sharing group. For different watchlists, it uses their actual union rather than assuming every viewer follows the same stocks. Separate groups must be calculated separately.</p>
<p>These are steady-state average HTTP request counts, not a bill, a measured benchmark, or proof that a plan has enough capacity. They omit retries, startup requests, other endpoints, and provider-specific charging units. Short-window burst limits also matter: three simultaneous requests and three evenly spaced requests have the same average but different burst behavior.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/9e21f3ce-5ede-464d-9993-ed049ee122db.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Calculated example, not a performance test. Twenty viewers follow the same 120 symbols, with 50 symbols per request and a five-second polling interval. Counts exclude retries and other traffic; shared delivery assumes compatible permissions.</em></p>
<h2>Share collection only across compatible requests</h2>
<p>The collector should combine subscriptions or polling demand for the same data, then distribute the results to authorized viewers. Keep the provider credential on the server. Each browser connects to the application using its own authenticated session, and the application checks what that viewer may receive.</p>
<p>A cache key needs enough context to prevent unlike records from colliding. Include the instrument identity and relevant feed, session, and data-mode settings. Separate entitlement groups when their access differs. A delayed feed and a real-time feed should not become interchangeable because they use the same ticker string.</p>
<p>Request sharing is an application design choice. It does not itself establish permission to display or redistribute a provider's data. Confirm that the intended audience and delivery model are allowed before combining viewers into one sharing group.</p>
<p>For a polling implementation, keep one refresh operation in flight per shared batch. If a slow response is still pending, a new screen refresh should reuse the available cache or wait for that operation rather than start an identical upstream request. When the last viewer leaves, remove unused demand after a deliberate grace period. In an application with several server instances, coordinate collection so each instance does not recreate the same upstream workload.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/00153b10-6bb9-4af2-b839-3af05e1dafec.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Suggested application architecture using TickDB snapshots as the source. The collector, cache, and viewer authorization belong to the application. Sharing requires compatible data permissions; the diagram does not assert redistribution rights or measured performance.</em></p>
<h2>A cache hit says nothing about market freshness</h2>
<p>Keep the provider timestamp, the time your server received the record, and the time the browser rendered it as separate values. TickDB documents its snapshot timestamp in Unix milliseconds. Preserve that value and confirm its event meaning for the selected data before using it to measure market-data latency.</p>
<p>A local cache lifetime tells the application when to attempt another fetch. It does not guarantee that the next response will contain a newer market observation. Polling every second can return the same last-trade price repeatedly, especially when a stock is inactive or its trading session has ended.</p>
<p>Display wording should reflect what the application knows. “Last checked” can describe a successful fetch. “Data as of” can show the provider timestamp. “Refresh failed” can explain why an older value remains visible. Do not overwrite the original timestamp with the current browser time and label the result live.</p>
<p>Choose freshness thresholds for the field and session being displayed. An old last trade during a quiet period is different from a failed connection. A watchlist can retain the last available value while showing that updates are unavailable; a feature that needs fresher input can decline to use it. Missing optional fields should remain unavailable rather than becoming zero.</p>
<h2>Streaming changes the workload but keeps the state problem</h2>
<p>A WebSocket stream can replace repeated snapshot requests when the application needs ongoing updates. TickDB documents a ticker channel with symbol-level subscribe and unsubscribe commands. A shared server connection can maintain the union of current demand, subject to the provider's connection and subscription limits and the application's data permissions.</p>
<p>Receiving events and repainting the screen are separate operations. The application can maintain its latest record for each symbol while rendering the visible watchlist at a manageable cadence. That is appropriate for a latest-value display. It is not a substitute for retaining every event when the task is trade analysis, auditing, or another event-complete workflow.</p>
<p>A connected socket alone does not establish that every requested symbol is subscribed or current. Track subscription outcomes and connection health separately from each record's timestamp. After a disconnect, show the loss of updates and follow the provider's recovery behavior before declaring the view current again. A new snapshot restores a latest-value view; it does not reconstruct events missed during the interruption.</p>
<h2>Handle limits without multiplying the load</h2>
<p>TickDB's error documentation distinguishes rate limiting, exhausted quota, connection limits, and subscription limits. For its rate-limit error, the documented action is to wait for the Retry-After duration. A permissions error or exhausted quota requires a different response; repeatedly retrying it will not create access.</p>
<p>Make the shared collector responsible for retries. If every waiting browser retries on its own, one failed refresh can turn into a burst of duplicate upstream requests. Use bounded retries and jitter where appropriate, reserve capacity for other work, and keep a visible degraded state while updates are unavailable. A quota budget should leave room for recovery traffic rather than allocate every request to routine refreshes.</p>
<h2>Open source does not remove the upstream limits</h2>
<p>Open-source stock-data clients are available. Databento, for example, publishes an open-source Python client for its market-data service. Such software can make integration easier to inspect and extend, but the client's license does not define the service's data access, request limits, or display permissions.</p>
<p>For a watchlist, inspect whether a client batches symbols, retries automatically, or creates one connection per instance. A wrapper can simplify a request while still duplicating the same work across viewers. The application remains responsible for its total demand and for how it presents cached values.</p>
<p>Before shipping, test two viewers requesting the same data at once, a refresh that takes longer than its interval, an omitted symbol, and an upstream limit response. Confirm that duplicate work stays controlled and that old values keep their original timestamps. The result should be a watchlist whose request volume and freshness labels remain understandable as more people open it.</p>
<p>Documentation basis: TickDB Ticker Snapshot, Real-Time Quotes and Error Codes, and the Databento Python client repository, checked November 5, 2026. The request-volume example is calculated from stated assumptions, not measured on a live service.</p>
]]></content:encoded></item><item><title><![CDATA[Stock API Charts Need Complete Downloads and Explicit Data Gaps]]></title><description><![CDATA[A stock chart can look convincing even when its history is incomplete. A stock API supplies records under specific retrieval and aggregation rules]]></description><link>https://tickdb.hashnode.dev/stock-api-charts-need-complete-downloads-and-explicit-data-gaps</link><guid isPermaLink="true">https://tickdb.hashnode.dev/stock-api-charts-need-complete-downloads-and-explicit-data-gaps</guid><category><![CDATA[Stock API]]></category><category><![CDATA[tickdb]]></category><category><![CDATA[stock market data,]]></category><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Wed, 04 Nov 2026 23:29:17 GMT</pubDate><content:encoded><![CDATA[<h1>Stock API Charts Need Complete Downloads and Explicit Data Gaps</h1>
<p><strong>Summary:</strong> A stock chart can look convincing even when its history is incomplete. A stock API supplies records under specific retrieval and aggregation rules; your application must finish the download and preserve missing observations. This article explains pagination, absent candles, and a small Python example that keeps an unknown price from becoming a false market move.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/60ecd8c1-3a1b-40b9-9c20-03d236c3eafa.png" alt="" style="display:block;margin:0 auto" />

<p>A stock API is a programmatic interface for retrieving stock-related data. For a historical chart, that often means requesting bars: records containing open, high, low, close, and volume for an interval. Those records can power chart viewers, research dashboards, and historical comparisons. They do not, by themselves, establish that every part of your requested window has been downloaded.</p>
<p>Consider a chart that has prices before and after a missing minute. Replacing the missing price with zero invents a collapse. Repeating the previous price creates a flat segment that was never observed. A sound implementation keeps the absence explicit while it determines whether the cause is normal market behavior, a retrieval problem, or a coverage limit.</p>
<h2>A successful response may contain only part of the history</h2>
<p>The first completeness check belongs in the downloader. An HTTP success means a request succeeded; a paginated endpoint may still have more records to return.</p>
<p>Alpaca's Historical bars documentation gives a concrete example. Its multi-symbol results are ordered by symbol and then timestamp, so the first page can contain only one symbol. The page limit applies across the response, and a short page can still have a continuation token. An application must follow the documented token rather than decide that fewer rows means the download is finished.</p>
<p>That behavior is specific to the endpoint. Other APIs use different retrieval controls. TickDB's Candlestick Data endpoint, for example, exposes a symbol, interval, time bounds, and a record limit. Its documentation caps the limit at 1,000 and says the Free plan cannot specify start or end times. These constraints matter when designing a history browser: access to recent candles does not automatically provide arbitrary historical navigation.</p>
<p>Keep retrieval logic in the provider adapter. Confirm how boundaries and ordering work before splitting a request into time windows. If that behavior is undocumented, treat it as an unresolved integration requirement; guessing how to advance a timestamp can skip records or repeatedly retrieve the same window.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/6dd3c31c-b974-444b-a8f3-75ff79dba231.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Suggested application flow. Retrieval follows the chosen endpoint's rules; validation and gap handling happen before rendering. This is a design illustration, not a measured system result.</em></p>
<h2>An absent candle does not identify its own cause</h2>
<p>Once retrieval has finished, a missing interval still needs interpretation. Databento's Aggregate bars documentation states that an interval without a trade produces no record. Under that convention, an absent bar can be an expected result. It does not establish that the service lost data, and the rule should not be assumed for every provider.</p>
<p>Start with the query context. Was the instrument trading during that period? Does the selected dataset cover the session you want? Does the provider omit intervals without qualifying observations? Did the downloader finish all required requests? These questions distinguish expected absence from an unexplained gap.</p>
<p>An exchange calendar also belongs in this interpretation. Do not generate a continuous minute grid across nights, weekends, and holidays and label every empty slot a failure. Use the calendar and session policy appropriate to the instrument. Keep timestamp conversion separate from session selection, including daylight saving changes.</p>
<p>Bar boundaries need their own check. Databento documents that its daily OHLCV schema follows UTC dates. A daily bucket therefore should not automatically be treated as the exchange-session day your chart intends to show.</p>
<p>Until the cause is established, a useful status is simply “no bar returned.” Labels such as “market closed” or “no trades” make stronger claims and need supporting context.</p>
<h2>Keep missing prices separate from derived display values</h2>
<p>Suppose a synthetic series has a close of 100.00 at 14:30 UTC, no returned bar at 14:31, and a close of 101.00 at 14:32. The missing slot contains no observed close. It is neither a zero nor proof that the price stayed at 100.00.</p>
<p>For a close-price line, pass an explicit missing value to the chart adapter and configure the renderer to show a break. Some charting systems connect surrounding observations unless told otherwise, so inspect the rendered result as well as the input array. For candlesticks, do not manufacture an OHLC record from a missing slot.</p>
<p>Forward filling can serve a different purpose, such as displaying a last-known value in a dashboard. Preserve its original observation time and mark it as carried forward. Store that derived value separately from the observed series so it cannot silently become a reported trade or candle.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/eb20a6c6-e7cd-4ca0-a718-7d4d4329a8d7.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Synthetic close-price example in UTC. The middle slot has no returned observation. Its cause is unknown from these records alone; it must not become an invented zero-price candle.</em></p>
<h2>A small adapter that preserves the missing slot</h2>
<p>The following Python example runs offline with synthetic data. It accepts an already selected, increasing UTC time grid and normalized close records for one instrument. It preserves missing slots, accepts identical duplicate records, and rejects conflicting duplicates instead of silently choosing a value.</p>
<pre><code class="language-python">from decimal import Decimal

def align_closes(slots, rows):
    if slots != sorted(set(slots)):
        raise ValueError("Slots must be unique and increasing")
    allowed = set(slots)
    observed = {}
    for row in rows:
        time = row["time"]
        if time not in allowed:
            raise ValueError("Bar outside the selected grid")
        close = Decimal(str(row["close"]))
        if not close.is_finite() or close &lt;= 0:
            raise ValueError("Expected a positive finite stock close")
        if time in observed and observed[time] != close:
            raise ValueError("Conflicting close for the same slot")
        observed[time] = close
    return [(time, observed.get(time)) for time in slots]

slots = ["14:30", "14:31", "14:32"]  # Synthetic UTC grid
rows = [
    {"time": "14:30", "close": "100.00"},
    {"time": "14:32", "close": "101.00"},
]
for time, close in align_closes(slots, rows):
    print(time, "MISSING" if close is None else close)
</code></pre>
<p>The output is <code>14:30 100.00</code>, <code>14:31 MISSING</code>, and <code>14:32 101.00</code> on separate lines. The function has preserved what the input says without claiming why the middle slot is absent.</p>
<p>This is a close-series example, not a downloader or exchange calendar. Production adapters should use full normalized timestamps rather than time-of-day strings, partition records by instrument and series settings, and validate the complete OHLCV record. A conflicting revision may be legitimate, but resolving it requires a documented revision policy. Raising an error here makes that decision visible.</p>
<h2>Open source helps with implementation but cannot restore absent history</h2>
<p>Open-source clients and data-processing tools can help build a stock API application. The distinction is between the software you can inspect or run and the underlying market-data service. An open-source client does not establish the upstream feed's coverage, usage rights, or historical depth.</p>
<p>For this charting task, inspect what the client does with pagination and missing values. A convenient wrapper may return a clean table while hiding a truncated query or a fill operation. Trace those transformations before using the result for research.</p>
<p>The same separation applies when using TickDB: the chart layer should retain the requested series settings and retrieval status alongside the returned candles. TickDB also documents that the final candle may still be forming. Mark that record as provisional when applicable; a changing current bar and an absent historical bar require different display behavior.</p>
<h2>Verify the window before calling the chart complete</h2>
<p>An acceptance check should cover more than whether the chart renders. Confirm that retrieval reached the endpoint's documented stopping condition, that the result is partitioned by the intended instrument and settings, and that unexplained gaps remain visible. Exercise an unfinished download, a missing slot, an identical duplicate, and a conflicting duplicate. Each should produce a deliberate result.</p>
<p>Do not turn the ratio of returned bars to clock minutes into a completeness score without establishing the expected-bar policy. A sparse instrument can legitimately have fewer bars under a trade-based aggregation rule. Conversely, a full-looking grid can conceal invented values.</p>
<p>The useful promise of a historical chart is that its displayed observations and limitations can be explained. A stock API supplies the records. The application must preserve the difference between a value that was observed, a value it derived, and a value it does not know.</p>
<p>Source basis: Alpaca Historical bars, Databento Aggregate bars, and TickDB Candlestick Data documentation, checked November 4, 2026. Code and numerical examples are synthetic.</p>
]]></content:encoded></item><item><title><![CDATA[Stock API Price Alerts Need State as Well as a Price Threshold]]></title><description><![CDATA[A stock API can supply prices for an alert, but a successful subscription does not define when a notification should fire.]]></description><link>https://tickdb.hashnode.dev/stock-api-price-alerts-need-state-as-well-as-a-price-threshold</link><guid isPermaLink="true">https://tickdb.hashnode.dev/stock-api-price-alerts-need-state-as-well-as-a-price-threshold</guid><category><![CDATA[stockmarket]]></category><category><![CDATA[marketdata]]></category><category><![CDATA[api]]></category><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Tue, 03 Nov 2026 23:57:17 GMT</pubDate><content:encoded><![CDATA[<h1>Stock API Price Alerts Need State as Well as a Price Threshold</h1>
<p><strong>Summary:</strong> A stock API can supply prices for an alert, but a successful subscription does not define when a notification should fire. A useful alert needs an observation type, a trigger rule, remembered state, and a policy for missing data. In a synthetic five-update example, checking whether the price exceeds $100 produces three matches, while detecting an observed upward crossing produces two events. The distinction prevents repeated updates above the threshold from becoming repeated crossing alerts.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/3891dd73-ba6b-4b22-b248-4164a51c1e7d.png" alt="" style="display:block;margin:0 auto" />

<h2>What the API contributes to a price alert</h2>
<p>A stock API gives software access to stock-related observations, such as recent prices, trades, quotes, or historical bars. A price alert is one application of that data. The API supplies observations; the application decides which observations qualify, whether the rule has changed state, and what message to deliver.</p>
<p>That division matters even for a small watchlist. The expression <code>price &gt; 100</code> answers whether the current value is above a threshold. It does not say whether this is the first time the application has seen that condition, whether a notification has already been sent, or what happened during a disconnection.</p>
<p>For a straightforward implementation, start with one instrument and one definition: notify when two consecutive eligible observations move from at or below $100 to above $100. “Eligible” means that the observations meet your chosen price type, feed, session, freshness, and ordering requirements. This is an observed crossing in the data your application accepts; it is not a claim that every market event was captured.</p>
<p>TickDB's ticker documentation, for example, provides stock messages with a symbol, a latest-price field, and a timestamp. Those are useful inputs to an alerting application. Reading that schema does not establish that a ticker update represents every individual trade, nor does it supply the application's notification policy.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/e493fc2d-1f50-4713-95bf-b2ca31d82a07.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Suggested application flow. Eligibility, trigger state, and notification delivery are separate responsibilities; the diagram is not a provider architecture or a delivery guarantee.</em></p>
<h2>Decide whether the user wants a condition or a crossing</h2>
<p>Suppose five accepted observations arrive in this order: $99, $101, $102, $100, and $101. These are synthetic values, not a market replay.</p>
<p>Checking <code>price &gt; 100</code> matches the second, third, and fifth observations. That can be appropriate for a status indicator that stays active while the condition is true. If each match sends a message, however, a user asking for an upward-crossing alert receives an extra notification at $102 even though the previous observation was already above $100.</p>
<p>A crossing rule compares the current observation with the previous eligible one. It fires at the second and fifth updates. The fourth update, exactly $100, puts the rule back on the at-or-below side. Under this definition, reaching $100 is not an upward crossing; moving above it is.</p>
<p>There are other valid policies. A one-time target alert can deactivate permanently after its first event. A condition alert can notify immediately when enabled above the threshold. A crossing alert can use the first observation only to establish a baseline. Choose one behavior and describe it in the interface. Quietly mixing them makes it difficult to explain apparently missing or repeated messages.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/1e00384c-6633-471b-b577-b1f4332f9e6a.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Synthetic observations for a threshold of $100. Values above the threshold occur three times; upward crossings between accepted observations occur twice.</em></p>
<h2>Choose data that can answer the rule</h2>
<p>An alert about the latest reported trade, the best ask, and a bar close represents three different tasks. A daily-close alert can use completed daily bars under an explicit session policy. An intraday trade alert needs observations appropriate to that definition. A latest-price field with unclear semantics is not enough to claim either behavior.</p>
<p>Polling also limits what the application can observe. Imagine two polls both return $99, while the price briefly rises to $101 and returns between them. Comparing the two snapshots cannot reveal that excursion. A bar high may indicate that the interval contained an above-threshold value, but it does not reconstruct every crossing or its exact sequence.</p>
<p>Streaming can reduce that sampling gap, but its usefulness still depends on the feed and channel. A ticker stream may summarize changes; a trade stream has its own coverage and filtering rules. Neither a WebSocket connection nor the word “real-time” proves complete observation of every relevant event.</p>
<p>Subscription success is another separate check. Alpaca's streaming documentation distinguishes connection, authentication, and subscription, and returns the active subscription list after changes. An application should verify the requested feed and symbols rather than treating an open socket as proof that the intended observations are arriving.</p>
<h2>A minimal observed-crossing detector</h2>
<p>The following Python example isolates the rule from the network and notification service. It requires only the standard library. Prices are synthetic decimal strings, and the caller must provide already validated, ordered observations for one rule. The threshold is fixed at $100 for this example.</p>
<pre><code class="language-python">from decimal import Decimal, InvalidOperation

class UpwardCrossing:
    def __init__(self):
        self.threshold = Decimal("100")
        self.previous = None

    def reset(self):
        self.previous = None

    def observe(self, text):
        try:
            price = Decimal(str(text))
        except InvalidOperation as exc:
            raise ValueError("Expected a decimal price") from exc
        if not price.is_finite() or price &lt;= 0:
            raise ValueError("Expected a finite positive price")
        crossed = (
            self.previous is not None
            and self.previous &lt;= self.threshold &lt; price
        )
        self.previous = price
        return crossed

# Synthetic observations; no API call or message delivery.
rule = UpwardCrossing()
prices = ["99", "101", "102", "100", "101"]
events = [i for i, p in enumerate(prices, 1) if rule.observe(p)]
print(events)  # [2, 5]
</code></pre>
<p>Repeated observations on the same side of the threshold do not produce another crossing. An initial value above $100 produces no event because the rule has no earlier baseline. Invalid numeric input raises an error without replacing the previous value. The surrounding application must decide whether that error also means continuity is uncertain and requires a reset.</p>
<p>Use separate state for each instrument and rule configuration. Do not feed several symbols through one instance or change the price definition while retaining the old baseline. This small detector does not validate timestamps, identify duplicate events, process corrections, persist state, or send notifications.</p>
<p>It also does not suppress repeated genuine oscillations around $100. A cooldown limits notification frequency, while hysteresis requires a lower rearming level before another trigger. Both change the user-visible policy. Add them only when their behavior matches the alert the user requested.</p>
<h2>A reconnect cannot fill in an unseen interval</h2>
<p>Suppose the last accepted value before a connection loss was $99 and the first value after recovery is $101. The endpoint observations are on different sides, but the application did not observe the intervening sequence. Its UI should not imply that it watched a specific crossing happen continuously.</p>
<p>One conservative policy is to mark the rule unavailable during the interruption, reconnect and restore the subscription, then use the first eligible observation as a new baseline. In the example, the caller invokes <code>reset()</code> when continuity is lost. The next call to <code>observe()</code> establishes state without firing. This deliberately sacrifices possible alerts during the gap in exchange for a clear live-observation policy.</p>
<p>TickDB's WebSocket guidance explicitly calls for resubscribing after reconnect and notes that full-market subscriptions receive another initial snapshot. That describes how to resume receiving data. A snapshot establishes current state; it does not reconstruct the sequence of missed observations. Where suitable history is available, a separate recovery process can evaluate it and label resulting notifications as retrospective.</p>
<p>The same policy question arises at startup, a session change, or a restart that loses in-memory state. Persisting the previous price alone is insufficient if you cannot establish continuity with the next observation. Record the interruption and the rule's status so that silence is not mistaken for evidence that the threshold was never crossed.</p>
<h2>Detecting an event and delivering it are separate outcomes</h2>
<p>An alert event can be detected correctly while the notification service is unavailable. Conversely, retrying a delivery after an uncertain response can produce duplicate messages even if the crossing detector ran only once.</p>
<p>For a durable implementation, persist the updated rule state and its alert event together, then let a delivery worker process that event with a stable identifier. Track pending, delivered, and failed attempts separately. Where the destination supports idempotency, reuse the event identifier on retries. If it does not, an ambiguous delivery result can still leave a duplicate risk; the price rule cannot solve that on its own.</p>
<p>This is an application design recommendation, not a guarantee supplied by a stock API or by the sample code. A market-data subscription also does not place a trade. Order submission and execution belong to a separate workflow with its own account permissions and outcomes.</p>
<h2>Verify the behavior the user will actually see</h2>
<p>Before connecting live data, run a short synthetic sequence through the rule and inspect the events. Include an initial value above the threshold, several values staying above it, a return to exactly the threshold, a new upward crossing, and a reset during a gap. Then exercise the surrounding application with stale inputs, out-of-order observations, a failed subscription, and notification retries.</p>
<p>An open source client can help receive and parse data, but it does not choose these policies for the application. The useful promise is precise: which observations qualify, what transition creates an event, when the rule is unavailable, and how delivery status is reported. That gives a stock API alert a meaning the user can understand and a behavior the developer can verify.</p>
<p>Sources consulted on November 3, 2026: TickDB Docs, Real-Time Quotes and WebSocket Best Practices; Alpaca, WebSocket Stream. The code and price sequences are offline demonstrations. No live feed completeness, latency, notification delivery, or trading performance is claimed.</p>
]]></content:encoded></item><item><title><![CDATA[Why Stock API Prices Differ and Which Price Your App Should Use]]></title><description><![CDATA[Why does a stock API show a different price from a chart or brokerage screen? The values may describe different trades, quotes, sessions, feeds]]></description><link>https://tickdb.hashnode.dev/why-stock-api-prices-differ-and-which-price-your-app-should-use</link><guid isPermaLink="true">https://tickdb.hashnode.dev/why-stock-api-prices-differ-and-which-price-your-app-should-use</guid><category><![CDATA[Stock API]]></category><category><![CDATA[stock price api]]></category><category><![CDATA[stock market data,]]></category><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Mon, 02 Nov 2026 23:53:12 GMT</pubDate><content:encoded><![CDATA[<h1>Why Stock API Prices Differ and Which Price Your App Should Use</h1>
<p><strong>Summary:</strong> Why does a stock API show a different price from a chart or brokerage screen? The values may describe different trades, quotes, sessions, feeds, or adjustment rules. Choose a price definition for each feature before comparing numbers: a watchlist can display the latest reported trade, a spread display needs bid and ask, and a historical chart needs a consistent bar and adjustment policy. A worked quote example shows why several valid prices can coexist for one stock.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/681ba8c5-17b8-4a3b-9a91-3d7d27510ca2.png" alt="" style="display:block;margin:0 auto" />

<h2>One stock can have several meaningful prices</h2>
<p>A stock API lets an application retrieve or subscribe to stock-related data. It does not make “the stock price” a single, universal field. The value you receive might represent a transaction, an available quote, an aggregate, or a historical series transformed for comparison over time.</p>
<p>Imagine a synthetic stock whose latest reported trade is $100.00. A subsequent quote shows a $99.98 bid and a $100.06 ask. Its previous regular-session close was $99.50. A watchlist, a spread display, and a daily-change calculation can legitimately use different numbers from this set.</p>
<p>The first question is what each number describes. A difference alone does not establish that a provider is wrong. Equally, different definitions should not become a blanket excuse for bad data: once the instrument, observation, and conventions are aligned, unexplained discrepancies deserve investigation.</p>
<h2>Trade prices and quoted prices answer different questions</h2>
<p>The last trade reports a completed transaction included by the feed. A bid is a quoted buying price; an ask is a quoted selling price. The best bid and ask refer to the coverage of the selected feed, which may be narrower than the overall market.</p>
<p>For the synthetic quote above, the spread is $0.08 and the midpoint is $100.02. The midpoint is an arithmetic reference derived from the two quote sides. There need not have been a trade at that price. The earlier $100.00 trade can remain the latest trade while the quote changes around it.</p>
<p>This distinction helps define an alert. “Notify me when a reported trade exceeds $100” and “notify me when the ask exceeds $100” describe different conditions. Choose the observation type explicitly, then decide how the application handles repeated updates and reconnects. A price alert also remains separate from an order: a displayed quote does not guarantee an execution at that price or for the desired quantity.</p>
<p>Alpaca's Latest Quotes reference makes the distinction concrete by describing best bid and ask prices and exposing a feed parameter. Its documented feed choices include IEX, SIP, and delayed SIP. The endpoint name alone therefore does not establish the venue coverage or freshness of a particular response.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/b0510610-c45a-42d5-9ede-f6c544a9bd8d.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Synthetic USD values for one stock. The midpoint and spread are calculated from the bid and ask; the last trade is a separate observation. No live market data is shown.</em></p>
<h2>A close needs an interval and a session</h2>
<p>In an OHLCV bar, the close is the closing value of a specified interval under the provider's aggregation rules. OHLCV stands for open, high, low, close, and volume. A one-minute close and a daily close summarize different intervals; neither is automatically the latest price available now.</p>
<p>A daily bar also needs a session definition. Ask whether it includes extended hours and whether the application needs an exchange's official closing value or the close of a vendor-generated bar. Those terms should not be treated as interchangeable without checking the documentation. Databento's schema guide notes that OHLCV results can vary with interval boundaries and the handling of trade breaks, halts, and periods with no trades.</p>
<p>For a daily-change feature, preserve the reference as well as the current value. In the synthetic example, comparing the $100.00 last trade with the $99.50 previous regular-session close produces approximately +0.50%. Substituting the $100.02 midpoint produces approximately +0.52%. Both calculations are arithmetically correct, but they describe different measures. Label the chosen measure and keep it consistent across the application.</p>
<p>TickDB's ticker documentation is an example of why field descriptions matter here. It defines <code>prev_close</code> as the previous close or reference price for the statistics window, when available. It also says that stock fields ending in <code>24h</code> usually refer to the trading day or session rather than a universal rolling 24-hour window. An application should establish that window before labeling a value “today's change.”</p>
<h2>Adjusted history serves a different purpose</h2>
<p>Historical charts often need to account for corporate actions. Suppose a stock undergoes a two-for-one split. Ignoring other price movement, a pre-split $100 share becomes two $50 shares. A raw price series can show a large step even though the split itself did not halve the holder's total position value.</p>
<p>A split-adjusted series can express earlier prices on a comparable share basis. Dividend adjustments address a different effect and should not be assumed to follow the same rules. Alpaca's Historical Bars reference, for example, exposes separate raw, split, and dividend settings.</p>
<p>Choose the transformation to fit the feature. A chart intended to show comparable prices across a split benefits from a stated adjustment policy. A question about what price actually printed at a historical moment calls for the corresponding raw observation. Do not join adjusted historical bars to an unadjusted live series without checking that their price bases align at the join.</p>
<h2>Give every application feature a price definition</h2>
<p>A small amount of explicit metadata prevents a large amount of ambiguity. Keep the instrument identity and currency, the observation type, the source feed, the relevant time, the session or interval, and the adjustment policy alongside the value. These can come from documented request settings as well as response fields; they should not be guessed from the number itself.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Price definition to choose</th>
<th>Label or context to preserve</th>
</tr>
</thead>
<tbody><tr>
<td>Watchlist</td>
<td>Latest reported trade or another explicitly defined latest price</td>
<td>Observation type, source, and time</td>
</tr>
<tr>
<td>Spread display</td>
<td>Bid and ask from one coherent quote</td>
<td>Feed coverage, quote time, and sizes when available</td>
</tr>
<tr>
<td>Daily change</td>
<td>Chosen current value versus a defined previous close</td>
<td>Both price definitions and the reference session</td>
</tr>
<tr>
<td>Historical chart</td>
<td>Bars with a fixed interval and adjustment policy</td>
<td>Session coverage and adjustment basis</td>
</tr>
</tbody></table>
<p>With TickDB or another provider, preserve useful source metadata during normalization. TickDB documents an extended-session field for US stocks with values such as <code>pre_market</code>, <code>post_market</code>, and <code>overnight</code>. That context can explain why a displayed update belongs to a different session from the chart beside it. If metadata is absent, record the uncertainty or resolve it from documented context instead of inventing a value.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/7b216552-054c-4715-aabe-46a9a681bbdf.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Suggested application design. Define the observation and its context before reducing a stock API response to a displayed number.</em></p>
<h2>A small quote calculation with an explicit boundary</h2>
<p>This Python example calculates the midpoint and spread of a synthetic quote. It uses only the standard library and an application-defined schema. It checks numeric inputs and refuses a crossed quote, where the bid exceeds the ask, because this simple display example does not attempt to interpret that condition.</p>
<pre><code class="language-python">from decimal import Decimal, InvalidOperation

def quote_metrics(bid_text, ask_text):
    try:
        bid = Decimal(str(bid_text))
        ask = Decimal(str(ask_text))
    except InvalidOperation as exc:
        raise ValueError("Expected decimal prices") from exc
    if not all(p.is_finite() and p &gt; 0 for p in (bid, ask)):
        raise ValueError("Expected finite positive prices")
    if bid &gt; ask:
        raise ValueError("Crossed quote requires review")
    return {"midpoint": (bid + ask) / 2, "spread": ask - bid}

# Synthetic USD quote; not a response from a live service.
metrics = quote_metrics("99.98", "100.06")
print(f"Midpoint: {metrics['midpoint']:.2f} USD")
print(f"Spread: {metrics['spread']:.2f} USD")
</code></pre>
<p>The output is <code>Midpoint: 100.02 USD</code> and <code>Spread: 0.08 USD</code>. The code deliberately does not call the midpoint a last trade or an executable price. Before applying it to live data, ensure both sides refer to the same instrument, currency, feed, and coherent quote state. On a stream that sends partial updates, maintaining that state requires the provider's update rules.</p>
<p>This calculation cannot certify quote freshness, available liquidity, or market-wide coverage. A zero spread is possible for a locked quote and is not rejected here; accepting that arithmetic result still says nothing about whether a trade can be executed. Input validation protects the calculation from obvious misuse, while market interpretation requires more context.</p>
<h2>When a difference becomes worth investigating</h2>
<p>To compare an API value with another screen, capture both observations and their metadata before refreshing either one. Match the instrument and currency, then the price type, feed scope, time, session, and adjustment basis. For historical bars, match the exact interval and aggregation conventions as well.</p>
<p>If those conditions align and a discrepancy remains, retain the original payloads and request parameters. Check delayed access, cache behavior, missing updates, corrections, and normalization errors. Two requests made at different moments are not a controlled comparison of the same event.</p>
<p>Changing to an open source client does not resolve these definitions automatically. A client can make requests easier to write, but it still consumes a particular upstream feed and must interpret that feed's records. The useful result for the user is a price with an understandable meaning: a latest trade, a bid and ask, a defined close, or an adjusted historical value, accompanied by enough context to use it correctly.</p>
<p>Sources consulted on November 2, 2026: Alpaca, Latest Quotes and Historical Bars; Databento, What's a Schema; TickDB Docs, Real-Time Quotes. All numerical examples and diagrams are synthetic or conceptual, not live measurements or supplier performance comparisons.</p>
]]></content:encoded></item><item><title><![CDATA[A Stock API Provides Market Data for Apps Within Feed and Usage Limits]]></title><description><![CDATA[A stock API gives an application programmatic access to prices and related financial data, but what can you build with it?]]></description><link>https://tickdb.hashnode.dev/a-stock-api-provides-market-data-for-apps-within-feed-and-usage-limits</link><guid isPermaLink="true">https://tickdb.hashnode.dev/a-stock-api-provides-market-data-for-apps-within-feed-and-usage-limits</guid><category><![CDATA[stock price api]]></category><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Mon, 02 Nov 2026 00:07:57 GMT</pubDate><content:encoded><![CDATA[<h1>A Stock API Provides Market Data for Apps Within Feed and Usage Limits</h1>
<p><strong>Summary:</strong> A stock API gives an application programmatic access to prices and related financial data, but what can you build with it? Charts, watchlists, research tools, and alerts are common uses; their reliability depends on the feed, timestamps, data definitions, and permitted use. A latest trade, a bid–ask quote, and a daily price bar describe different things, while an open source client library grants no automatic rights to redistribute the underlying data.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/7a18282f-32ae-4898-a85f-e2110a0fb323.png" alt="" style="display:block;margin:0 auto" />

<h2>What a stock API actually provides</h2>
<p>A stock API is an interface through which software requests or receives stock-related data. An application can use it to retrieve recent prices, download historical observations, or subscribe to updates. Some services also expose company fundamentals, corporate actions, or news through separate endpoints.</p>
<p>The interface makes data accessible to code. The feed behind it determines which events are represented, how quickly they arrive, and how much history is available. Two services can both return a price for the same ticker while describing different observations.</p>
<p>An endpoint is a particular operation offered by the service, such as retrieving historical bars. Many APIs deliver a structured response, often JSON, that a program can parse into fields. An SDK is a client library that wraps those operations in convenient functions. Installing an SDK does not change the coverage or permissions of the underlying service.</p>
<p>It also helps to separate market data from trading. Reading a price does not submit an order. A trading application may use a market data API alongside a broker API for account information, order placement, and execution reports. Even when one provider offers both, these are separate capabilities.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/c050e94b-133b-4b4a-8b27-c9d65099cbe8.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. A conceptual market data path. Feed coverage and normalization affect what reaches the application; a trade and a quote describe different events.</em></p>
<h2>The data type determines the feature you can build</h2>
<p>A trade records a completed transaction. A quote describes a bid and an ask within the coverage of the feed. A price bar summarizes observations over an interval, typically as open, high, low, close, and volume, or OHLCV. These records serve different purposes.</p>
<p>A daily bar is useful for a long-term chart, but it cannot reveal the sequence of price changes inside that day. A last-trade price can support a watchlist, but it is not a promise that a new order will execute at that price. That distinction matters as soon as a display becomes an input to an alert or trading decision.</p>
<table>
<thead>
<tr>
<th>Application</th>
<th>Useful data</th>
<th>Important boundary</th>
</tr>
</thead>
<tbody><tr>
<td>Watchlist</td>
<td>Latest prices and timestamps</td>
<td>Show when the observation was recorded and whether it is delayed.</td>
</tr>
<tr>
<td>Historical chart</td>
<td>OHLCV bars</td>
<td>Keep interval, session, and adjustment settings consistent.</td>
</tr>
<tr>
<td>Price alert</td>
<td>A defined trade or quote stream</td>
<td>Handle disconnects and decide which observation triggers the alert.</td>
</tr>
<tr>
<td>Portfolio view</td>
<td>Prices plus holdings</td>
<td>Holdings and cash flows come from a separate source unless explicitly provided.</td>
</tr>
<tr>
<td>Research or backtesting</td>
<td>Historical observations and reference data</td>
<td>Check missing periods, corporate actions, and which securities existed at the time.</td>
</tr>
</tbody></table>
<p>For example, TickDB documents a ticker stream with stock symbols, latest prices, and timestamps, including examples for US stocks, Hong Kong stocks, and A-shares. This illustrates the input a watchlist can consume. The application still has to interpret the fields, maintain its own display state, and explain the age of the observation to the reader.</p>
<h2>REST and WebSocket describe delivery methods</h2>
<p>With a REST-style HTTP API, the client makes a request and receives a response. This fits an on-demand lookup or a historical download. Repeating the request creates polling: the application checks again at an interval it chooses.</p>
<p>A WebSocket connection lets the server send updates over a persistent connection. This can suit a watchlist or alert service that needs frequent changes without repeatedly asking for the same symbols. The client must still manage subscriptions, detect a broken connection, and recover according to the provider's protocol. Reconnecting does not by itself prove that missed events have been recovered.</p>
<p>Neither method establishes the quality of the feed. A WebSocket can carry delayed data, and an HTTP response can contain a recent observation. Similarly, transport latency and market coverage are separate questions. Alpaca's Market Data documentation, for example, distinguishes real-time IEX coverage on its Basic equities plan from broader exchange coverage on another plan. “Real-time” alone does not identify the set of venues represented.</p>
<h2>A small example that keeps the timestamp visible</h2>
<p>A watchlist should retain the observation time alongside the price. Otherwise, a successful request can make an old observation look current simply because the screen has just refreshed.</p>
<p>The following Python example runs offline using synthetic data and an application-defined schema. It requires only the standard library. It calculates observation age and applies an illustrative 60-second display threshold; it does not measure network latency or decide whether the market should be trading.</p>
<pre><code class="language-python">from datetime import datetime, timezone
from decimal import Decimal

def format_observation(row, now, max_age_seconds=60):
    observed = datetime.fromisoformat(
        row["observed_at"].replace("Z", "+00:00")
    )
    if observed.utcoffset() is None or now.utcoffset() is None:
        raise ValueError("Timezone-aware timestamps required")
    price = Decimal(str(row["last_price"]))
    if not price.is_finite() or price &lt;= 0:
        raise ValueError("Expected a finite positive stock price")
    age = (now - observed).total_seconds()
    if age &lt; 0:
        state = "check clock or timestamp semantics"
    elif age &gt; max_age_seconds:
        state = "older than display threshold"
    else:
        state = "within display threshold"
    return f"{row['symbol']} {price} | age={age:.0f}s | {state}"

# Synthetic observation and clock; not a live API response.
sample = {
    "symbol": "EXAMPLE.US",
    "last_price": "100.25",
    "observed_at": "2026-10-28T14:30:00Z",
}
now = datetime(2026, 10, 28, 14, 31, 15, tzinfo=timezone.utc)
print(format_observation(sample, now))
</code></pre>
<p>The output is <code>EXAMPLE.US 100.25 | age=75s | older than display threshold</code>. The displayed number is still available, but the application no longer presents its recency ambiguously.</p>
<p>For a real integration, map the provider's fields into this small internal schema only after checking their meanings. TickDB's ticker documentation specifies Unix milliseconds for its timestamp field and describes latest prices as strings. Converting milliseconds as if they were seconds would invalidate the age calculation. Its documentation also notes that a <code>24h</code> field suffix does not imply a rolling 24-hour window in every market; stock statistics usually refer to a trading day or session.</p>
<p>The threshold remains an application policy. An old last trade can be normal after a session ends or when an instrument has not traded recently. Use a market calendar and the feed's timestamp definition before turning an age label into a service-health warning. This example also leaves ingestion validation, symbol mapping, persistence, and recovery to the surrounding application.</p>
<h2>Is there an open source stock API available</h2>
<p>Yes, open source software exists for accessing stock data. The useful distinction is whether you mean an open source client, a server you can host, or a dataset with permissions that fit your project.</p>
<p>For example, yfinance is an open source Python library for accessing Yahoo Finance data. Its repository states that it is not affiliated with Yahoo, describes its research and educational purpose, and directs users to Yahoo's terms for rights to downloaded data. The repository also notes that the Yahoo Finance API is intended for personal use. The software license and the terms governing the data are separate.</p>
<p>Hosting your own API around downloaded data gives you control over your application interface. It does not create new data rights, improve the original feed's coverage, or guarantee that its upstream service will remain available. A free hosted API is a different option again: it may offer access subject to quotas and conditions without publishing its server implementation.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/690a6894-919c-4cf8-bcc6-d8edc0ceafbb.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Open source software, access to a data service, and permission to use the data are separate considerations.</em></p>
<h2>Limitations that matter after the first successful request</h2>
<p><strong>Coverage and freshness need separate checks.</strong> Confirm which markets, venues, instruments, and sessions the feed represents. A response labeled “latest” may contain the most recent available observation rather than an observation from the present moment. Extended-hours support also needs an explicit check; a regular-session chart should not silently mix in a different session.</p>
<p><strong>Historical data has definitions and revisions.</strong> A split changes the relationship between share count and price. Dividend adjustments serve another purpose. Alpaca's historical bars reference exposes separate adjustment options, which illustrates why a chart should record its settings instead of relying on an unnoticed default. For a backtest, also check delisted instruments and information availability dates; today's list of surviving companies is not automatically an appropriate historical universe.</p>
<p><strong>Capacity depends on the workload.</strong> Suppose a dashboard polls 120 symbols individually every 10 seconds. That is 720 requests per minute before retries. If an endpoint supports all 120 symbols in one batch, the same schedule requires six requests per minute, although the provider may meter symbols or data volume separately. Streaming reduces repeated polling but introduces connection and subscription limits of its own.</p>
<p><strong>Access and public use are different requirements.</strong> An API key proves that a service accepts a credential for a request. It does not, on its own, establish permission for public display, redistribution, or every commercial use. Check the relevant data agreement for the intended application before building a public product around a personal-use prototype.</p>
<p>Start with a feature narrow enough to verify: one chart, a small watchlist, or one alert rule. Record its required data type, acceptable observation age, session coverage, and intended use. Then check both a normal response and an empty, delayed, or interrupted one. A stock API is useful when those conditions match the application—not merely when a request returns a price.</p>
<p>Sources consulted on November 1, 2026: Bavest, Stock API – What You Need to Know; Databento, Stock Market API and schema documentation; Alpaca, About Market Data API and Historical Bars; TickDB Docs, Real-Time Quotes; yfinance repository README. Examples and diagrams are explanatory, not live market measurements.</p>
]]></content:encoded></item><item><title><![CDATA[How to Integrate TickDB: From API Key to a Validated Python Quote]]></title><description><![CDATA[Adding market data to an application starts with a small, verifiable request. With an API key that has access to your chosen instrument, TickDB’s REST…]]></description><link>https://tickdb.hashnode.dev/how-to-integrate-tickdb-from-api-key-to-a-validated-python-quote</link><guid isPermaLink="true">https://tickdb.hashnode.dev/how-to-integrate-tickdb-from-api-key-to-a-validated-python-quote</guid><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Thu, 29 Oct 2026 10:17:17 GMT</pubDate><content:encoded><![CDATA[<h1>How to Integrate TickDB: From API Key to a Validated Python Quote</h1>
<p>Adding market data to an application starts with a small, verifiable request. With an API key that has access to your chosen instrument, TickDB’s REST snapshot endpoint provides the symbol, latest trade price, and market timestamp needed for a basic quote display. This tutorial builds a Python function around that request, checks both HTTP and API errors, and preserves the timestamp so the application can distinguish a returned price from a newly updated one.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/fb7426d4-b947-4e4f-8095-4905af683ed7.png" alt="" style="display:block;margin:0 auto" />

<p>A useful first milestone is simple: request one instrument, return a validated price and its timestamp, and fail clearly when the request cannot be fulfilled. Starting here makes it easier to diagnose credentials, permissions, and response handling before introducing a watchlist or continuous updates.</p>
<p>The example below uses <a href="http://AAPL.US">AAPL.US</a> and a server-side Python script. It follows the documented API contract; running it against the service requires your own valid key and applicable market access.</p>
<h2>1. Create a key and keep it with the backend</h2>
<p>Create an API key in the TickDB console, then confirm that it can access the instrument you intend to request. An issued key does not by itself establish access to every market or endpoint.</p>
<p>For a web application, call the provider from your backend. The browser should receive the quote data it needs, while the backend holds the credential. This also gives you one place to enforce request limits and decide how to handle temporary failures.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/7027a382-24b5-4544-bd16-25b639bfe3c1.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Suggested application architecture. The API key stays with your backend; quote data returns to the application. This is a design illustration, not an official deployment diagram.</em></p>
<p>Create a local Python environment and install the HTTP client:</p>
<pre><code class="language-bash">python3 -m venv .venv
source .venv/bin/activate
python -m pip install requests
export TICKDB_API_KEY='replace_with_your_api_key'
</code></pre>
<p>These activation and environment-variable commands use a macOS or Linux shell. Use the equivalent commands for your operating system. For deployment, inject the key through your service’s secret configuration; do not commit the value to source control.</p>
<h2>2. Verify the smallest request first</h2>
<p>REST requests authenticate with the X-API-Key header. The snapshot endpoint takes a required symbols parameter; the following request explicitly selects the stock product type.</p>
<pre><code class="language-bash">curl --silent --show-error \
  --connect-timeout 5 --max-time 20 \
  -H "X-API-Key: $TICKDB_API_KEY" \
  'https://api.tickdb.ai/v1/market/ticker?symbols=AAPL.US&amp;type=stock'
</code></pre>
<p>Inspect the response before adding more code. A successful response has code equal to 0 and a data array. For this request, look for a row whose symbol is <a href="http://AAPL.US">AAPL.US</a> and check that last_price and timestamp are present. The price is represented as a string, and the timestamp uses Unix milliseconds.</p>
<p>Other fields, such as bid_price, ask_price, and volume_24h, depend on the instrument, trading session, and data availability. A missing optional field should remain unavailable in your application; replacing it with zero changes its meaning.</p>
<h2>3. Turn the request into a reusable function</h2>
<p>Save the following as tickdb_quote.py. It performs one request, rejects HTTP and business-level failures, and returns a small object that a quote display can consume. It deliberately leaves automatic retries to the caller so that a failed request cannot silently turn into an uncontrolled polling loop.</p>
<pre><code class="language-python">import json
import os
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation

import requests

URL = "https://api.tickdb.ai/v1/market/ticker"


def fetch_quote(symbol, api_key):
    response = requests.get(
        URL,
        params={"symbols": symbol, "type": "stock"},
        headers={"X-API-Key": api_key},
        timeout=(5, 15),
        allow_redirects=False,
    )
    try:
        body = response.json()
    except ValueError as exc:
        raise RuntimeError(
            f"HTTP {response.status_code}: non-JSON response"
        ) from exc
    if not isinstance(body, dict):
        raise RuntimeError("Expected a JSON object")
    code = str(body.get("code", "UNKNOWN"))
    if not 200 &lt;= response.status_code &lt; 300 or code != "0":
        retry_after = response.headers.get("Retry-After")
        raise RuntimeError(
            f"HTTP {response.status_code}, code={code}, "
            f"Retry-After={retry_after}"
        )
    rows = body.get("data")
    if not isinstance(rows, list):
        raise RuntimeError("Expected a quote list")
    matches = [row for row in rows if isinstance(row, dict)
               and row.get("symbol") == symbol]
    if len(matches) != 1:
        raise RuntimeError("Expected one matching quote")
    row = matches[0]
    try:
        price = Decimal(row["last_price"])
        timestamp = row["timestamp"]
        if not price.is_finite():
            raise ValueError("Non-finite price")
        if type(timestamp) is not int or timestamp &lt;= 0:
            raise ValueError("Invalid timestamp")
        quote_time = datetime.fromtimestamp(
            timestamp / 1000, tz=timezone.utc
        )
    except (KeyError, TypeError, ValueError, InvalidOperation,
            OverflowError, OSError) as exc:
        raise RuntimeError("Invalid quote fields") from exc
    return {
        "symbol": row["symbol"],
        "last_price": str(price),
        "market_time_utc": quote_time.isoformat(),
    }


if __name__ == "__main__":
    key = os.environ.get("TICKDB_API_KEY", "").strip()
    if not key:
        raise SystemExit("Set TICKDB_API_KEY first")
    try:
        print(json.dumps(fetch_quote("AAPL.US", key), indent=2))
    except (requests.RequestException, RuntimeError) as exc:
        raise SystemExit(f"Quote request failed: {exc}")
</code></pre>
<p>Run it from the same environment:</p>
<pre><code class="language-bash">python tickdb_quote.py
</code></pre>
<p>On success, the script prints a JSON object containing symbol, last_price, and market_time_utc. The values depend on the actual service response; no fixed example price is an expected result.</p>
<p>There are three details worth keeping when you move this function into a larger application. First, it checks the API code even when HTTP succeeds. Second, it matches the requested symbol explicitly, so an empty response cannot become a misleading quote. Third, it parses the price with Decimal and serializes it back to text, avoiding an unnecessary conversion through binary floating point.</p>
<p>The connection and read timeouts bound those individual waits; they are not a strict deadline for the entire operation. Add an application-level deadline if your service needs one.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/d253600e-57e7-424a-bdd0-f0810fa386f3.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. A response must pass transport, API, and field checks before the application uses it. The market timestamp remains attached to the quote.</em></p>
<h2>4. Handle failures according to their cause</h2>
<p>Repeating the same request is useful only when the underlying failure can change. If authentication fails, check the key and header. If the service reports insufficient permission, verify the key’s market and endpoint access. If a symbol is ambiguous, use the returned available product types to correct the request instead of guessing.</p>
<p>For rate-limit errors, use the Retry-After response header when supplied and reduce the request rate. Temporary service failures may justify a bounded retry with increasing delay. Set an attempt limit and an overall deadline; authentication, permission, and malformed-parameter errors need correction rather than repeated traffic.</p>
<p>The function reports the HTTP status, normalized API code, and Retry-After value. It does not print the credential. A production wrapper can preserve those fields as structured error attributes, then choose the appropriate response without parsing human-readable message text.</p>
<h2>5. Connect the quote to a real application</h2>
<p>For a quote card, map last_price to the displayed value and market_time_utc to an “as of” label. Keep a separate local fetch time if you need to show when your application last contacted the service. Replacing the market timestamp with the fetch time would make an older quote look newly generated.</p>
<p>An older market timestamp does not automatically identify an API failure. The market may be closed, the instrument may not have traded recently, or the selected session may have different update behavior. Check the relevant trading session and your data access conditions before deciding that a price is stale. The elapsed time since the market timestamp is also not a measurement of network latency.</p>
<p>When you extend the integration to a watchlist, the documented snapshot limit is 50 instruments per request. Batch within that limit, preserve symbol identifiers, and explicitly handle requested instruments that have no usable row. Your refresh interval must also fit your account’s request allowance. Repeated snapshots do not reconstruct every trade between requests.</p>
<p>Before connecting the function to a dashboard, verify a successful response, an invalid credential, a missing quote, and a temporary failure in a controlled test. The UI should show an unavailable or error state when the request fails; it should never present a fabricated zero price as a successful quote.</p>
<p>A first TickDB integration is ready for the next stage when it returns the intended instrument, preserves price and time semantics, and exposes failures clearly. That gives a dashboard, research script, or backend service a defined data contract to build on.</p>
<p>Source basis: TickDB Quick Start, Market Snapshot, and Error Codes documentation, checked October 29, 2026.</p>
]]></content:encoded></item><item><title><![CDATA[Integrate a Stock API in Python: Fetch Quotes, Validate Responses, Preserve Timestamps]]></title><description><![CDATA[Summary: How do you turn a stock API response into a usable quote display? Keep the key on the server, validate the response, match records by symbol, and…]]></description><link>https://tickdb.hashnode.dev/integrate-a-stock-api-in-python-fetch-quotes-validate-responses-preserve-timestamps</link><guid isPermaLink="true">https://tickdb.hashnode.dev/integrate-a-stock-api-in-python-fetch-quotes-validate-responses-preserve-timestamps</guid><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Tue, 27 Oct 2026 14:22:21 GMT</pubDate><content:encoded><![CDATA[<h1>Integrate a Stock API in Python: Fetch Quotes, Validate Responses, Preserve Timestamps</h1>
<p><strong>Summary:</strong> How do you turn a stock API response into a usable quote display? Keep the key on the server, validate the response, match records by symbol, and show the price with its quote time. A live TickDB request for <a href="http://AAPL.US">AAPL.US</a> and <a href="http://MSFT.US">MSFT.US</a> returned both symbols successfully, but its top-level quote timestamps were older than its pre-market timestamps. The runnable Python example below makes that distinction visible and handles missing or malformed records.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/e90e069d-9d40-4386-8561-b05456b6c8e3.png" alt="" style="display:block;margin:0 auto" />

<p>A useful first integration has a small, testable goal: request two stocks and produce one result for each, including the price, quote timestamp, and validation status. Start with a single REST snapshot. Once the data is interpreted correctly, the same parsing layer can feed a watchlist or a backend endpoint.</p>
<p>TickDB supplies the market data in this example. The client uses its documented ticker endpoint; the validation and output rules are application code. An <code>OK</code> result means the record passed these checks. It does not certify that the price is fresh enough for every use case.</p>
<h2>1. Set up the request</h2>
<p>Use Python 3.10 or later. The example uses the standard library, so there are no third-party packages to install. Create an API key in the provider dashboard and confirm that the account has access to the requested instruments. Keep that key in the server environment, away from frontend bundles and published notebooks.</p>
<p>The request uses <code>X-API-Key</code> authentication, <code>symbols=AAPL.US,MSFT.US</code>, and <code>type=stock</code>. The ticker endpoint accepts up to 50 symbols per request. This tutorial fixes the watchlist at two symbols so that request construction and response handling remain easy to inspect.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/14f335d1-26f3-4847-99fc-41614a86117f.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Application flow for a single snapshot request. This is a design illustration, not a service architecture or performance measurement.</em></p>
<h2>2. Fetch and validate the response</h2>
<p>Save the following complete program as <code>stock_quote.py</code>. It checks HTTP errors in the request layer and the API business code in the parser. These are separate checks: receiving JSON is not sufficient evidence of a successful data request.</p>
<p>The parser uses <code>Decimal</code> for price validation and converts documented millisecond timestamps to UTC. It rejects duplicate or unexpected symbols, marks absent records as <code>MISSING</code>, and marks unusable required fields as <code>INVALID</code>. Optional fields such as bid, ask, or volume are not needed for this initial display.</p>
<pre><code class="language-python">import argparse
import json
import os
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

SYMBOLS = ['AAPL.US', 'MSFT.US']
ENDPOINT = 'https://api.tickdb.ai/v1/market/ticker'


def fetch():
    key = os.environ.get('TICKDB_API_KEY', '').strip()
    if not key:
        raise ValueError('Set TICKDB_API_KEY before a live request')
    query = urlencode({'symbols': ','.join(SYMBOLS), 'type': 'stock'})
    req = Request(ENDPOINT + '?' + query,
                  headers={'X-API-Key': key})
    try:
        with urlopen(req, timeout=15) as response:
            return json.load(response)
    except HTTPError as exc:
        detail = 'HTTP ' + str(exc.code)
        if exc.code == 429:
            detail += '; Retry-After=' + str(exc.headers.get('Retry-After'))
        raise ValueError(detail) from None
    except (URLError, TimeoutError):
        raise ValueError('Network request failed; check connectivity') from None


def normalize(payload):
    if not isinstance(payload, dict):
        raise ValueError('Expected a response object')
    if str(payload.get('code')) != '0':
        raise ValueError('API code=' + str(payload.get('code')))
    rows = payload.get('data')
    if not isinstance(rows, list):
        raise ValueError('Expected data array')
    by_symbol = {}
    for row in rows:
        if not isinstance(row, dict):
            raise ValueError('Expected quote object')
        symbol = row.get('symbol')
        if not isinstance(symbol, str) or symbol not in SYMBOLS:
            raise ValueError('Unexpected symbol')
        if symbol in by_symbol:
            raise ValueError('Duplicate symbol: ' + symbol)
        by_symbol[symbol] = row
    result = []
    for symbol in SYMBOLS:
        row = by_symbol.get(symbol)
        if row is None:
            result.append({'symbol': symbol, 'status': 'MISSING'})
            continue
        try:
            price = Decimal(str(row['last_price']))
            ts = row['timestamp']
            if not price.is_finite() or price &lt;= 0:
                raise ValueError('Invalid stock price')
            if type(ts) is not int or ts &lt;= 0:
                raise ValueError('Invalid millisecond timestamp')
            stamp = datetime.fromtimestamp(ts / 1000, timezone.utc)
        except (KeyError, InvalidOperation, ValueError, OverflowError, OSError):
            result.append({'symbol': symbol, 'status': 'INVALID'})
            continue
        result.append({'symbol': symbol, 'status': 'OK',
                       'last_price': str(price),
                       'quote_time_utc': stamp.isoformat()})
    return result


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--input', help='Replay a saved API JSON response')
    args = parser.parse_args()
    try:
        payload = (json.loads(Path(args.input).read_text())
                   if args.input else fetch())
        print(json.dumps(normalize(payload), indent=2))
    except (ValueError, OSError) as exc:
        parser.exit(1, 'Error: ' + str(exc) + '\n')


if __name__ == '__main__':
    main()
</code></pre>
<p>On macOS or Linux, run it from the directory containing the file:</p>
<pre><code class="language-bash">export TICKDB_API_KEY='YOUR_API_KEY'
python3 stock_quote.py
</code></pre>
<p>Replace the placeholder locally. For a deployed application, inject the key through its secret configuration. The program makes one request and exits; it does not implement a refresh loop or automatic retries.</p>
<h2>3. Read an actual response before adding a UI</h2>
<p>For this article, a live TickDB MCP <code>get_ticker</code> request used the same symbols and product type. It ran on October 27, 2026, between 10:11:04 and 10:11:05 UTC, and returned <code>code: 0</code> with both records. The following values are selected directly from that response, not simulated prices:</p>
<table>
<thead>
<tr>
<th>Symbol</th>
<th>Top-level last_price</th>
<th>Top-level timestamp (UTC)</th>
</tr>
</thead>
<tbody><tr>
<td><a href="http://AAPL.US">AAPL.US</a></td>
<td>343.91</td>
<td>2026-10-26 20:00:01</td>
</tr>
<tr>
<td><a href="http://MSFT.US">MSFT.US</a></td>
<td>532.6</td>
<td>2026-10-26 20:00:00</td>
</tr>
</tbody></table>
<p>The same response also contained <code>pre_market_quote.last_done</code> values of <code>342.65</code> for <a href="http://AAPL.US">AAPL.US</a> and <code>528.08</code> for <a href="http://MSFT.US">MSFT.US</a>. Their timestamps were October 27 at 10:11:03 and 10:10:44 UTC, respectively. A newer request did not make every price field represent the same session.</p>
<p>The program deliberately reads only the top-level <code>last_price</code> and <code>timestamp</code>. If the display needs pre-market prices, select the nested pre-market price and its corresponding timestamp together, and label that session. Do not combine a top-level price with the timestamp from a different quote object. The gap between request time and quote time is also not a measurement of network latency.</p>
<p>The saved live response is included as <code>evidence/live-response.json</code>. Replay it without a key:</p>
<pre><code class="language-bash">python3 stock_quote.py --input evidence/live-response.json
</code></pre>
<p>The replay produces two <code>OK</code> records with the prices and UTC timestamps shown above. Online data retrieval was verified through TickDB MCP; the Python parser was executed against the saved real response. The Python script's direct HTTP path was not authenticated against the live service during this validation.</p>
<h2>4. Match returned records by symbol</h2>
<p>Batch responses deserve a completeness check. The documented response follows the order of valid requested symbols, which does not justify assuming every requested symbol occupies its original array position. Build a dictionary by <code>symbol</code>, then walk the original watchlist.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/b8250850-a5aa-4360-876a-25665c9168f4.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Illustrative partial-response case. The live request above returned both stocks; this diagram explains the application's missing-record behavior.</em></p>
<p>This approach preserves the requested display order and keeps an absent quote from becoming a zero price or being assigned to the wrong company. For this fixed-symbol tutorial, an unexpected symbol fails the batch. A larger application can quarantine the unexpected record and report a separate validation error.</p>
<h2>5. Decide what happens after a failure</h2>
<p>An authentication or permission failure needs a configuration check. Repeatedly sending the same request will not repair an invalid key or grant missing access. A rate-limit response needs a delayed retry; the example reports <code>Retry-After</code> when supplied and exits. A production scheduler should honor that instruction and bound its retry attempts. Timeouts and server failures also need explicit retry limits.</p>
<p>For a watchlist, preserve the last successful value with its original timestamp when refresh fails, and display the failure alongside it. Never advance the quote timestamp just because a refresh was attempted. A trading calendar and a session-specific freshness policy are needed before classifying an old quote as abnormal; this example does not implement those rules.</p>
<p>The integration is ready for its first UI when each requested stock has an explicit result, every displayed price retains its matching timestamp, and a failed request cannot silently become a fresh-looking quote. Those checks give a stock API response a clear meaning before the application adds polling, caching, or streaming.</p>
<p>Source basis: TickDB Quick Start, Ticker Snapshot, and Error Codes documentation, checked October 27, 2026; live ticker response retrieved on the same date.</p>
]]></content:encoded></item><item><title><![CDATA[XAUUSD Explained for Price Tracking and Alerts]]></title><description><![CDATA[Summary: XAUUSD quotes gold in US dollars per troy ounce. It is useful for following gold prices, building dashboards, and setting price alerts, but the…]]></description><link>https://tickdb.hashnode.dev/xauusd-explained-for-price-tracking-and-alerts</link><guid isPermaLink="true">https://tickdb.hashnode.dev/xauusd-explained-for-price-tracking-and-alerts</guid><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Mon, 26 Oct 2026 09:16:07 GMT</pubDate><content:encoded><![CDATA[<h1>XAUUSD Explained for Price Tracking and Alerts</h1>
<p><strong>Summary:</strong> XAUUSD quotes gold in US dollars per troy ounce. It is useful for following gold prices, building dashboards, and setting price alerts, but the symbol alone does not identify an executable price or tell you whether to buy or sell. For example, a synthetic bid of 2,400.00 and ask of 2,400.80 produce a midpoint of 2,400.40, but neither that calculation nor a threshold alert guarantees an execution price.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/f4393150-4aa2-4697-a0c3-ca05ecd4ad00.png" alt="" style="display:block;margin:0 auto" />

<p>XAUUSD is the common market symbol for gold priced in US dollars. A quote of 2,400 would mean 2,400 US dollars per troy ounce of gold; that number is an illustration, not a live price. XAU identifies gold and USD identifies the currency used to express its value.</p>
<p>For someone searching for XAUUSD, the practical question is usually what the displayed number represents and what they can do with it. A quote can help monitor the gold market. Before using it in an alert or a trading decision, however, you need to know which feed produced it, what kind of price it is, and when it was updated.</p>
<h2>What the symbol tells you</h2>
<p>XAUUSD describes a price relationship. It does not, by itself, specify a broker, contract size, trading venue, or settlement arrangement. A chart provider may show a particular contributor's gold quote, while a broker may use the same label for a product with its own trading terms.</p>
<p>The price unit and the trade size are separate. A price quoted per troy ounce does not mean that every order represents one ounce. For an actual position, the contract specification determines the exposure. That distinction matters when estimating profit and loss: a one-dollar price move has different consequences for different position sizes.</p>
<p>Several gold-related prices can therefore appear on screen without representing the same thing:</p>
<table>
<thead>
<tr>
<th>Price or product</th>
<th>What it represents</th>
<th>What to check</th>
</tr>
</thead>
<tbody><tr>
<td>XAUUSD spot quote</td>
<td>Gold priced in dollars on a specified feed</td>
<td>Source, price field, timestamp</td>
</tr>
<tr>
<td>Broker gold product</td>
<td>A tradable instrument under that broker's terms</td>
<td>Bid/ask, contract size, charges, trading hours</td>
</tr>
<tr>
<td>Gold futures</td>
<td>An exchange contract with defined terms and expiry</td>
<td>Exact contract and delivery month</td>
</tr>
<tr>
<td>LBMA Gold Price</td>
<td>A benchmark established through scheduled auctions</td>
<td>Benchmark date and auction session</td>
</tr>
</tbody></table>
<p>The LBMA Gold Price is set twice daily, at 10:30 and 15:00 London time. It serves a different purpose from a continuously updating quote. Likewise, a gold futures chart can differ from a spot chart because it describes a different contract. Matching the word “gold” is not enough to make two prices comparable.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/c933f2b5-046e-4bb1-b026-39df9fc0df32.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Gold-related prices need their own context. This is a conceptual comparison, not a live market display.</em></p>
<h2>What XAUUSD is right now depends on the source and time</h2>
<p>A useful answer to “What is XAUUSD right now?” includes a value, its source, its price type, and a timestamp with a timezone. An article cannot keep a static number current. In a live application, show those details next to the price and make the age of the last update visible.</p>
<p>Bid is the quoted buying price from the quoting side; ask is its quoted selling price. For a customer trading against that quote, buying normally refers to the ask and selling to the bid, subject to the broker's execution terms. The midpoint is the arithmetic average of the two. It can be convenient for a chart, but it is not a promise that a trade will execute there.</p>
<p>Consider a synthetic bid of 2,400.00 and ask of 2,400.80. The midpoint is 2,400.40 and the spread is 0.80 dollars per troy ounce. An alert based on the midpoint can trigger at a different moment from an alert based on the bid or ask. The application needs to name the field used by its rule.</p>
<p>Differences between websites do not automatically mean that one is wrong. They may use different contributors, different update times, or different price fields. Compare those details before treating the difference as a data error or a trading opportunity. A news headline saying gold crossed a level is also historical context, not evidence of the current executable price.</p>
<h2>Useful applications for XAUUSD data</h2>
<p>A price dashboard is a natural starting point. Display the chosen price with its unit, source and update time; when updates stop, preserve the last observation and change its status. A frozen number should not keep the same visual treatment as a fresh observation.</p>
<p>Price alerts add a second requirement: a precisely defined condition. “Notify me when the ask crosses above my threshold” is a clearer rule than “notify me when gold goes up.” Only evaluate accepted observations, and remember the previous accepted state to avoid sending the same notification on every update. After a gap, label the first observation appropriately; it does not reveal the exact time at which a threshold was crossed during the missing interval.</p>
<p>Historical charts and event reviews are also useful. Keep the price basis, timezone and candle boundaries consistent when comparing periods. If a data field describes tick activity or a particular contributor's volume, do not relabel it as total global gold turnover.</p>
<p>TickDB is one possible market-data input for these applications. Its current documentation lists XAUUSD for ticker snapshots and shows a metals WebSocket example with bid, ask and a timestamp. That supports considering it as a quote source; it does not establish a guaranteed execution price or prove the quality of an application built on top of it.</p>
<h2>A small check before updating the display</h2>
<p>The following Python 3 example accepts a normalized quote and computes its midpoint, spread and freshness status. It uses only the standard library. The five-second age limit is an illustrative application policy, not a market rule or a vendor latency promise. The supplied prices and timestamps are synthetic.</p>
<pre><code class="language-python">from decimal import Decimal, InvalidOperation


def quote_view(q, now_ms, max_age_ms=5000):
    """Normalized input; the five-second limit is a demo policy."""
    try:
        if q["symbol"] != "XAUUSD" or not q["source"]:
            raise ValueError("wrong symbol or missing source")
        ts = q["timestamp_ms"]
        if type(ts) is not int or ts &lt;= 0:
            raise ValueError("invalid timestamp")
        bid = Decimal(str(q["bid"]))
        ask = Decimal(str(q["ask"]))
        if not bid.is_finite() or not ask.is_finite():
            raise ValueError("non-finite price")
        if bid &lt;= 0 or ask &lt; bid:
            raise ValueError("invalid bid/ask")
    except (KeyError, TypeError, ValueError, InvalidOperation):
        return {"status": "invalid", "alert_allowed": False}
    age = now_ms - ts
    status = "clock_error" if age &lt; 0 else (
        "stale" if age &gt; max_age_ms else "fresh")
    return {"status": status, "age_ms": age,
            "mid": str((bid + ask) / 2),
            "spread": str(ask - bid),
            "alert_allowed": status == "fresh"}


if __name__ == "__main__":
    # Synthetic data, not a current market quote.
    q = {"symbol": "XAUUSD", "source": "demo",
         "bid": "2400.00", "ask": "2400.80",
         "timestamp_ms": 1800000000000}
    print(quote_view(q, now_ms=1800000002000))
</code></pre>
<p>This input produces <code>fresh</code>, an age of 2,000 milliseconds, a midpoint of <code>2400.40</code>, a spread of <code>0.80</code>, and <code>alert_allowed=True</code>. Missing bid or ask values produce <code>invalid</code>; a quote older than the chosen limit produces <code>stale</code>. A future timestamp produces <code>clock_error</code>, so a clock mismatch does not silently become a fresh observation.</p>
<p>When adapting a TickDB response, use the documented field names and millisecond timestamp convention rather than assuming it already matches this example's normalized input. Bid and ask are conditional fields in the documentation. If either is absent, the application can show an unavailable state; it should not manufacture a midpoint from missing values or substitute zero. The example does not call an online API.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/aa05fed2-de40-4a18-a6fc-ca7fe435b42c.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Suggested application flow. Data checks and alert eligibility are application logic; this is not a vendor architecture or a performance test.</em></p>
<p>Freshness must also be reevaluated on a timer. If the check runs only when a new message arrives, a disconnected feed can leave the last result marked fresh indefinitely. For a production feed, add duplicate and out-of-order handling, connection state, session-aware rules and clock monitoring. This function checks one observation; it does not implement a complete alert service.</p>
<h2>Why a quote cannot tell you to buy or sell today</h2>
<p>“Should I buy or sell XAUUSD today?” asks for a decision that a current quote cannot settle. The same price can support different decisions for someone closing an existing exposure, someone following a tested intraday rule, and someone simply watching the market. A price observation contains no statement about the reader's horizon, acceptable loss, costs or existing position.</p>
<p>An indicator or news headline does not remove those missing inputs. Before treating an alert as a trade instruction, a strategy would need explicit entry and exit conditions, position sizing, realistic spread and slippage assumptions, and evaluation outside the data used to design it. A threshold crossing on a reference feed remains an observed condition; any trade must use the actual product's terms and available execution prices.</p>
<p>For a dependable XAUUSD application, the first milestone is smaller and verifiable: a reader can tell which gold price is displayed, when it was observed, and whether it is eligible for the chosen calculation. When any of those details is missing, show the uncertainty and pause the affected alert. That gives the quote a clear, useful role without asking it to answer a question it cannot resolve.</p>
<p>Source notes: TradingView XAUUSD overview and news pages; LBMA daily auction price information; CME Group gold futures information; TickDB ticker documentation. Reviewed October 26, 2026. Numerical examples are synthetic.</p>
]]></content:encoded></item><item><title><![CDATA[Forex WebSocket Integration: Stream EUR/USD Quotes with Node.js and Recover After Disconnects]]></title><description><![CDATA[Abstract: A live forex display needs a client that authenticates, subscribes to the right quote channel, and restores that subscription after a disconnect…]]></description><link>https://tickdb.hashnode.dev/forex-websocket-integration-stream-eur-usd-quotes-with-node-js-and-recover-after-disconnects</link><guid isPermaLink="true">https://tickdb.hashnode.dev/forex-websocket-integration-stream-eur-usd-quotes-with-node-js-and-recover-after-disconnects</guid><dc:creator><![CDATA[wang vball]]></dc:creator><pubDate>Sun, 25 Oct 2026 09:50:20 GMT</pubDate><content:encoded><![CDATA[<h1>Forex WebSocket Integration: Stream EUR/USD Quotes with Node.js and Recover After Disconnects</h1>
<p><strong>Abstract:</strong> A live forex display needs a client that authenticates, subscribes to the right quote channel, and restores that subscription after a disconnect. A server-side Node.js collector can do this while keeping credentials out of the browser and tracking connection health separately from quote freshness. The implementation below uses documented EUR/USD bid/ask fields, validates incoming messages, and checks an application-level heartbeat; reconnecting resumes collection but does not recover missing ticks.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/4614668c-2343-499e-997b-0609e5b9f586.png" alt="" style="display:block;margin:0 auto" />

<p>A useful first integration has a narrow output: one JSON record for each accepted EUR/USD quote, with bid, ask, calculated midpoint, spread, source timestamp, and local receipt time. Once that collector behaves correctly, a dashboard or alert service can consume the same records without handling the provider's wire protocol itself.</p>
<p>This tutorial uses TickDB as the concrete forex input. Its documented <code>ticker</code> channel includes EUR/USD bid and ask fields, and its WebSocket connection authenticates with an API key in the connection URL. The implementation is a server-side example. Keep the key in the collector's environment and keep full connection URLs out of logs.</p>
<h2>1. Choose the quote stream before writing the client</h2>
<p>A forex WebSocket is a persistent transport for a provider's messages. It does not define a universal subscription format. Tiingo's forex documentation describes top-of-book updates with an array payload; Massive documents separate forex feeds for quotes and aggregate bars. Their message contracts are not interchangeable.</p>
<p>For a bid/ask display, start with quotes. A bar contains a summary over a time interval and cannot replace the current two-sided quote. Keep bid and ask separate in your application model: a calculated midpoint is useful for display, but it is not an executable price. Likewise, do not relabel a provider's latest-price field as a transaction price without confirming its meaning.</p>
<p>Before running the example, have Node.js, npm, and an API key with permission for the requested market and channel. Confirm subscription and connection limits for your account. A successful WebSocket handshake alone does not establish that the requested subscription has been accepted.</p>
<h2>2. Connect, subscribe, and normalize one currency pair</h2>
<p>The collector connects over TLS, sends a <code>ticker</code> subscription for <code>EURUSD</code>, then routes each incoming message by <code>cmd</code>. A <code>pong</code> updates connection health, an <code>error</code> stops the example for inspection, and a <code>ticker</code> is passed to the quote parser. Keeping these paths separate prevents control messages from being treated as prices.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/44464c88-bc3f-469a-bb7c-8d250cb3f15a.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 1. Suggested integration architecture. The example implements the collector; the dashboard or alert consumer is a later integration step.</em></p>
<p>Create a project directory and install the WebSocket library:</p>
<pre><code class="language-bash">mkdir forex-websocket-demo
cd forex-websocket-demo
npm init -y
npm install ws@8
</code></pre>
<p>Save the following complete program as <code>forex_ws.cjs</code>. The <code>.cjs</code> extension makes the CommonJS module format explicit, even inside a project that otherwise uses ES modules.</p>
<pre><code class="language-javascript">const WebSocket = require('ws');

function normalize(d) {
  if (!d || d.symbol !== 'EURUSD' || d.type !== 'forex') return null;
  const price = v =&gt; (typeof v === 'string' &amp;&amp; v.trim()) ||
    typeof v === 'number' ? Number(v) : NaN;
  const bid = price(d.bid_price), ask = price(d.ask_price);
  const t = d.timestamp;
  if (![bid, ask].every(Number.isFinite) || bid &lt;= 0 || ask &lt; bid ||
      !Number.isSafeInteger(t) || t &lt;= 0 || t &gt; 8.64e15) return null;
  return { symbol: d.symbol, bid, ask, mid: (bid + ask) / 2,
    spread: ask - bid, event_ms: t, received_ms: Date.now() };
}

function start(apiKey, options = {}) {
  const url = new URL(options.url || 'wss://api.tickdb.ai/v1/realtime');
  url.searchParams.set('api_key', apiKey);
  let socket, retry, attempts = 0, stopped = false;
  const emit = options.emit || (row =&gt; console.log(JSON.stringify(row)));
  const log = options.log || (text =&gt; console.error(text));
  const delay = options.delay || (n =&gt; n &lt;= 3 ? 1000 : n &lt;= 6 ? 5000 : 30000);
  function connect() {
    if (stopped) return;
    const ws = socket = new WebSocket(url, { handshakeTimeout: 15000 });
    let heartbeat, deadline, latest = 0;
    ws.on('unexpected-response', (_req, res) =&gt; {
      if ([401, 403].includes(res.statusCode)) stopped = true;
      log(`Handshake HTTP ${res.statusCode}`);
      res.resume();
      ws.terminate();
    });
    ws.on('open', () =&gt; {
      attempts = 0;
      log('Connected; subscribing to EURUSD');
      ws.send(JSON.stringify({ cmd: 'subscribe', data: {
        channel: 'ticker', symbols: ['EURUSD'] } }));
      heartbeat = setInterval(() =&gt; {
        if (ws.readyState !== WebSocket.OPEN || deadline) return;
        ws.send(JSON.stringify({ cmd: 'ping' }));
        deadline = setTimeout(() =&gt; ws.terminate(), options.timeout || 20000);
      }, options.heartbeat || 60000);
    });
    ws.on('message', raw =&gt; {
      let packet;
      try { packet = JSON.parse(raw.toString()); }
      catch { log('Skipped malformed JSON'); return; }
      for (const item of Array.isArray(packet) ? packet : [packet]) {
        if (!item || typeof item !== 'object') continue;
        if (item.cmd === 'pong') {
          clearTimeout(deadline); deadline = undefined;
        } else if (item.cmd === 'error') {
          log(`Server error code ${String(item.code)}`);
          // Stop for inspection; do not retry a rejected subscription blindly.
          stopped = true; ws.terminate(); return;
        } else if (item.cmd === 'ticker') {
          const row = normalize(item.data);
          if (!row) { log('Skipped invalid or unexpected quote'); continue; }
          if (row.event_ms &lt; latest) { log('Skipped older quote'); continue; }
          latest = row.event_ms;
          emit(row);
        }
      }
    });
    // Avoid printing error messages that might contain the credential URL.
    ws.on('error', () =&gt; log('WebSocket transport error'));
    ws.on('close', () =&gt; {
      clearInterval(heartbeat); clearTimeout(deadline);
      log('Disconnected; mark cached quotes stale');
      if (!stopped) retry = setTimeout(connect, delay(++attempts));
    });
  }
  connect();
  return () =&gt; { stopped = true; clearTimeout(retry); socket?.terminate(); };
}

if (require.main === module) {
  const key = process.env.TICKDB_API_KEY;
  if (!key) { console.error('Set TICKDB_API_KEY first'); process.exit(1); }
  const stop = start(key);
  process.once('SIGINT', stop);
  process.once('SIGTERM', stop);
}
module.exports = { normalize, start };
</code></pre>
<p>Set the key in your terminal environment or secret manager, then run the collector. The following commands use POSIX shell syntax; replace the placeholder locally and do not commit the key:</p>
<pre><code class="language-bash">export TICKDB_API_KEY='YOUR_API_KEY'
node forex_ws.cjs
</code></pre>
<p>For a file sink, run <code>node forex_ws.cjs &gt; quotes.jsonl</code>. Quote records go to standard output; connection diagnostics go to standard error. Use Ctrl+C to stop the collector and cancel future retries. This is a low-volume tutorial sink, not a durable ingestion service: before expanding the subscription list, add a bounded queue and handle storage backpressure.</p>
<p>The source includes optional parameters so a local simulation can supply a loopback endpoint and shorter timers. Normal command-line execution always uses the documented endpoint. The code and recovery paths were checked with constructed messages and a local WebSocket server; a credentialed live connection was not tested for this article.</p>
<h2>3. Interpret the output without inventing price semantics</h2>
<p>The parser requires the requested symbol, the forex product type, finite positive prices, an ask at least as high as the bid, and a valid positive integer timestamp in milliseconds. Missing or empty price fields are rejected instead of turning into zero. These are application validation rules; a rejected quote should be counted and investigated if it persists.</p>
<p>For a constructed input with bid <code>1.10000</code> and ask <code>1.10020</code>, the midpoint is <code>1.10010</code> and the spread is <code>0.00020</code> in price units. With the conventional EUR/USD pip size of <code>0.0001</code>, that spread is two pips. The program leaves the result in price units so the same label is not accidentally applied to a pair with a different pip convention. JavaScript floating-point subtraction may print extra decimal digits; format at the display boundary, and use decimal arithmetic for calculations that require exact decimal values.</p>
<p><code>event_ms</code> preserves the provider's timestamp. <code>received_ms</code> records the collector's local wall-clock time. Their difference is a useful diagnostic only after checking clock synchronization and timestamp semantics; it is not a measurement of pure network latency. Store the raw timestamp before formatting it into a local display timezone.</p>
<p>Within each connection, the example skips messages older than the last accepted timestamp. It allows equal timestamps because a millisecond timestamp is not a unique event ID. This provides a simple latest-quote policy, not complete deduplication. The timestamp watermark resets on reconnect; applications that require ordering across sessions need a separate persistence and reconciliation policy.</p>
<h2>4. Recover the connection and expose stale data</h2>
<p>Every successful reconnect sends the subscription again. The sample follows the documented retry stages: one second for the first three unsuccessful reconnect attempts, five seconds for the next three, and thirty seconds thereafter. A successful connection resets the counter. For a deployed service, also monitor repeated short-lived connections so an open-close loop does not go unnoticed.</p>
<p>The heartbeat sends the JSON command <code>ping</code> every sixty seconds and terminates the connection if its corresponding JSON <code>pong</code> does not arrive within twenty seconds. These are configurable application checks. WebSocket protocol Ping/Pong frames are handled separately by the library; receiving one does not make an old EUR/USD quote fresh.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6adb789c998dc4e227bca889/a65509e6-e071-4eca-9b35-0ef2ec67534a.png" alt="" style="display:block;margin:0 auto" />

<p><em>Figure 2. Recovery resumes future updates. Keep cached prices marked stale until your application accepts a sufficiently fresh quote; the gap is not automatically backfilled.</em></p>
<p>A dashboard should therefore maintain two indicators: whether the connection is healthy, and when the last usable quote arrived. This collector prints connection changes and quote timestamps; the consuming application must implement the visible stale state. On a disconnect, retain the last price only with a stale label. After reconnecting, evaluate the new quote's age before marking it current.</p>
<p>Choose the freshness threshold for the pair, session, and application. A quiet market or a weekend can produce no new quotes while the heartbeat still succeeds. Reconnecting repeatedly because no price changed will not create new market activity.</p>
<p>The example deliberately stops on any server error message. Read the reported code and consult the error documentation before restarting. TickDB documents handshake HTTP 401/403 for authentication or access failures and code <code>4005</code> for a channel permission failure. Repeating the same rejected subscription cannot repair those conditions. A production client can classify transient errors for bounded retries, but should keep permission and configuration errors out of that loop.</p>
<h2>5. Verify behavior before connecting a dashboard</h2>
<p>Run the first live check during an active forex session. Look for the requested pair, sensible bid/ask values, timestamps in the expected unit, and continuing valid records. If the socket opens but no quotes arrive, inspect subscription feedback, account access, symbol format, and the session before changing the parser.</p>
<p>Then interrupt the network briefly. The collector should report disconnection, reconnect, and send the same subscription again. Test an unavailable or invalid key separately: it should stop on HTTP 401/403 rather than repeatedly attempting authentication. In a local simulation, send malformed JSON, missing prices, an older timestamp, and no heartbeat response; check each handling path without depending on live market activity.</p>
<p>A current quote after reconnect is evidence that collection resumed. It says nothing about the quotes that occurred during the outage. If your application needs continuous bars or a tick archive, define what historical data can actually be retrieved, reconcile the missing interval, and leave unrecoverable gaps explicitly marked. For a live display, the practical completion criterion is narrower: the requested quote stream resumes, stale state is visible, and no missing interval is presented as uninterrupted coverage.</p>
<p>Source notes: TickDB Ticker, WebSocket Best Practices, Ping, and Error Codes; Tiingo Real-time Forex WebSocket API Documentation; Massive Forex WebSocket Overview; and the ws project documentation. Checked October 25, 2026. Examples illustrate integration behavior, not execution quality or provider performance.</p>
]]></content:encoded></item></channel></rss>